Merge branch 'recloudstream:master' into githubAccount

This commit is contained in:
antonydp 2022-11-02 14:37:12 +01:00 committed by GitHub
commit 3f4d28b7df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 2131 additions and 1650 deletions

76
.github/workflows/build_to_archive.yml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Archive build
on:
push:
branches: [ master ]
paths-ignore:
- '*.md'
- '*.json'
- '**/wcokey.txt'
workflow_dispatch:
concurrency:
group: "Archive-build"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Generate access token
id: generate_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/secrets"
- name: Generate access token (archive)
id: generate_archive_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
repository: "recloudstream/cloudstream-archive"
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Fetch keystore
id: fetch_keystore
run: |
TMP_KEYSTORE_FILE_PATH="${RUNNER_TEMP}"/keystore
mkdir -p "${TMP_KEYSTORE_FILE_PATH}"
curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "${TMP_KEYSTORE_FILE_PATH}/prerelease_keystore.keystore" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore.jks"
curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "keystore_password.txt" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore_password.txt"
KEY_PWD="$(cat keystore_password.txt)"
echo "::add-mask::${KEY_PWD}"
echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT
- name: Run Gradle
run: |
./gradlew assemblePrerelease
env:
SIGNING_KEY_ALIAS: "key0"
SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
SIGNING_STORE_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
- uses: actions/checkout@v3
with:
repository: "recloudstream/cloudstream-archive"
token: ${{ steps.generate_archive_token.outputs.token }}
path: "archive"
- name: Move build
run: |
cp app/build/outputs/apk/prerelease/release/*.apk "archive/$(git rev-parse --short HEAD).apk"
- name: Push archive
run: |
cd $GITHUB_WORKSPACE/archive
git config --local user.email "actions@github.com"
git config --local user.name "GitHub Actions"
git add .
git commit --amend -m "Build $GITHUB_SHA" || exit 0 # do not error if nothing to commit
git push --force

View file

@ -155,10 +155,10 @@ dependencies {
// implementation("androidx.leanback:leanback-paging:1.1.0-alpha09") // implementation("androidx.leanback:leanback-paging:1.1.0-alpha09")
// Exoplayer // Exoplayer
implementation("com.google.android.exoplayer:exoplayer:2.16.1") implementation("com.google.android.exoplayer:exoplayer:2.18.1")
implementation("com.google.android.exoplayer:extension-cast:2.16.1") implementation("com.google.android.exoplayer:extension-cast:2.18.1")
implementation("com.google.android.exoplayer:extension-mediasession:2.16.1") implementation("com.google.android.exoplayer:extension-mediasession:2.18.1")
implementation("com.google.android.exoplayer:extension-okhttp:2.16.1") implementation("com.google.android.exoplayer:extension-okhttp:2.18.1")
//implementation("com.google.android.exoplayer:extension-leanback:2.14.0") //implementation("com.google.android.exoplayer:extension-leanback:2.14.0")
@ -176,7 +176,9 @@ dependencies {
implementation("com.jaredrummler:colorpicker:1.1.0") implementation("com.jaredrummler:colorpicker:1.1.0")
//run JS //run JS
implementation("org.mozilla:rhino:1.7.14") // do not upgrade to 1.7.14, since in 1.7.14 Rhino uses the `SourceVersion` class, which is not
// available on Android (even when using desugaring), and `NoClassDefFoundError` is thrown
implementation("org.mozilla:rhino:1.7.13")
// TorrentStream // TorrentStream
//implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
@ -211,9 +213,9 @@ dependencies {
// slow af yt // slow af yt
//implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT") //implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
// newpipe yt // newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L190
implementation("com.github.TeamNewPipe:NewPipeExtractor:dev-SNAPSHOT") implementation("com.github.TeamNewPipe:NewPipeExtractor:9ffdd0948b2ecd82655f5ff2a3e127b2b7695d5b")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
// Library/extensions searching with Levenshtein distance // Library/extensions searching with Levenshtein distance
implementation("me.xdrop:fuzzywuzzy:1.4.0") implementation("me.xdrop:fuzzywuzzy:1.4.0")

View file

@ -18,7 +18,6 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SubtitleHelper
import okhttp3.Interceptor import okhttp3.Interceptor
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -31,6 +30,12 @@ const val USER_AGENT =
val mapper = JsonMapper.builder().addModule(KotlinModule()) val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!! .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
/**
* Defines the constant for the all languages preference, if this is set then it is
* the equivalent of all languages being set
**/
const val AllLanguagesName = "universal"
object APIHolder { object APIHolder {
val unixTime: Long val unixTime: Long
get() = System.currentTimeMillis() / 1000L get() = System.currentTimeMillis() / 1000L
@ -159,7 +164,8 @@ object APIHolder {
val hashSet = HashSet<String>() val hashSet = HashSet<String>()
val activeLangs = getApiProviderLangSettings() val activeLangs = getApiProviderLangSettings()
hashSet.addAll(apis.filter { activeLangs.contains(it.lang) }.map { it.name }) val hasUniversal = activeLangs.contains(AllLanguagesName)
hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }.map { it.name })
/*val set = settingsManager.getStringSet( /*val set = settingsManager.getStringSet(
this.getString(R.string.search_providers_list_key), this.getString(R.string.search_providers_list_key),
@ -193,26 +199,17 @@ object APIHolder {
return list.filter { names.contains(it) }.map { DubStatus.valueOf(it) }.toHashSet() return list.filter { names.contains(it) }.map { DubStatus.valueOf(it) }.toHashSet()
} }
/**
* Gets all the activated provider languages
* Used to obey the preference provider_lang_key
* but it turned out too complicated and unnecessary with extensions.
**/
fun Context.getApiProviderLangSettings(): HashSet<String> { fun Context.getApiProviderLangSettings(): HashSet<String> {
val langs = apis.map { it.lang }.toSet() val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } val hashSet = hashSetOf(AllLanguagesName) // def is all languages
return langs.toHashSet()
// val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
// val hashSet = HashSet<String>()
// hashSet.add("en") // def is only en // hashSet.add("en") // def is only en
// val list = settingsManager.getStringSet( val list = settingsManager.getStringSet(
// this.getString(R.string.provider_lang_key), this.getString(R.string.provider_lang_key),
// hashSet.toMutableSet() hashSet
// ) )
//
// if (list.isNullOrEmpty()) return hashSet if (list.isNullOrEmpty()) return hashSet
// return list.toHashSet() return list.toHashSet()
} }
fun Context.getApiTypeSettings(): HashSet<TvType> { fun Context.getApiTypeSettings(): HashSet<TvType> {
@ -254,7 +251,8 @@ object APIHolder {
null null
} ?: default } ?: default
val langs = this.getApiProviderLangSettings() val langs = this.getApiProviderLangSettings()
val allApis = apis.filter { langs.contains(it.lang) } val hasUniversal = langs.contains(AllLanguagesName)
val allApis = apis.filter { hasUniversal || langs.contains(it.lang) }
.filter { api -> api.hasMainPage || !hasHomePageIsRequired } .filter { api -> api.hasMainPage || !hasHomePageIsRequired }
return if (currentPrefMedia.isEmpty()) { return if (currentPrefMedia.isEmpty()) {
allApis allApis

View file

@ -0,0 +1,72 @@
package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
open class Jeniusplay : ExtractorApi() {
override val name = "Jeniusplay"
override val mainUrl = "https://jeniusplay.com"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val document = app.get(url, referer = "$mainUrl/").document
val hash = url.split("/").last().substringAfter("data=")
val m3uLink = app.post(
url = "$mainUrl/player/index.php?data=$hash&do=getVideo",
data = mapOf("hash" to hash, "r" to "$referer"),
referer = url,
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
).parsed<ResponseSource>().videoSource
M3u8Helper.generateM3u8(
this.name,
m3uLink,
url,
).forEach(callback)
document.select("script").map { script ->
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
val subData =
getAndUnpack(script.data()).substringAfter("\"tracks\":[").substringBefore("],")
tryParseJson<List<Tracks>>("[$subData]")?.map { subtitle ->
subtitleCallback.invoke(
SubtitleFile(
getLanguage(subtitle.label ?: ""),
subtitle.file
)
)
}
}
}
}
private fun getLanguage(str: String): String {
return when {
str.contains("indonesia", true) || str
.contains("bahasa", true) -> "Indonesian"
else -> str
}
}
data class ResponseSource(
@JsonProperty("hls") val hls: Boolean,
@JsonProperty("videoSource") val videoSource: String,
@JsonProperty("securedLink") val securedLink: String?,
)
data class Tracks(
@JsonProperty("kind") val kind: String?,
@JsonProperty("file") val file: String,
@JsonProperty("label") val label: String?,
)
}

View file

@ -6,7 +6,11 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
class Moviehab : ExtractorApi() { class MoviehabNet : Moviehab() {
override var mainUrl = "https://play.moviehab.net"
}
open class Moviehab : ExtractorApi() {
override var name = "Moviehab" override var name = "Moviehab"
override var mainUrl = "https://play.moviehab.com" override var mainUrl = "https://play.moviehab.com"
override val requiresReferer = false override val requiresReferer = false

View file

@ -46,6 +46,7 @@ open class YoutubeExtractor : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
println("TRYING TO ExTRACT: $url")
if (ytVideos[url].isNullOrEmpty()) { if (ytVideos[url].isNullOrEmpty()) {
val link = val link =
YoutubeStreamLinkHandlerFactory.getInstance().fromUrl( YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(

View file

@ -1,40 +1,39 @@
package com.lagradost.cloudstream3.plugins package com.lagradost.cloudstream3.plugins
import android.app.* import android.app.*
import dalvik.system.PathClassLoader import android.content.Context
import com.google.gson.Gson
import android.content.res.AssetManager import android.content.res.AssetManager
import android.content.res.Resources import android.content.res.Resources
import android.os.Environment
import android.widget.Toast
import android.content.Context
import android.os.Build import android.os.Build
import android.os.Environment
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.google.gson.Gson
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER
import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile
import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
import dalvik.system.PathClassLoader
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
@ -217,7 +216,7 @@ object PluginManager {
* 3. If outdated download and load the plugin * 3. If outdated download and load the plugin
* 4. Else load the plugin normally * 4. Else load the plugin normally
**/ **/
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) = ioSafe { fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
// Load all plugins as fast as possible! // Load all plugins as fast as possible!
loadAllOnlinePlugins(activity) loadAllOnlinePlugins(activity)
@ -227,7 +226,7 @@ object PluginManager {
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES ?: emptyArray()) + PREBUILT_REPOSITORIES
val onlinePlugins = urls.toList().amap { val onlinePlugins = urls.toList().apmap {
getRepoPlugins(it.url)?.toList() ?: emptyList() getRepoPlugins(it.url)?.toList() ?: emptyList()
}.flatten().distinctBy { it.second.url } }.flatten().distinctBy { it.second.url }
@ -248,7 +247,7 @@ object PluginManager {
val updatedPlugins = mutableListOf<String>() val updatedPlugins = mutableListOf<String>()
outdatedPlugins.amap { pluginData -> outdatedPlugins.apmap { pluginData ->
if (pluginData.isDisabled) { if (pluginData.isDisabled) {
//updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name)) //updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name))
unloadPlugin(pluginData.savedData.filePath) unloadPlugin(pluginData.savedData.filePath)
@ -279,9 +278,9 @@ object PluginManager {
/** /**
* Use updateAllOnlinePluginsAndLoadThem * Use updateAllOnlinePluginsAndLoadThem
* */ * */
fun loadAllOnlinePlugins(activity: Activity) = ioSafe { fun loadAllOnlinePlugins(activity: Activity) {
// Load all plugins as fast as possible! // Load all plugins as fast as possible!
(getPluginsOnline()).toList().amap { pluginData -> (getPluginsOnline()).toList().apmap { pluginData ->
loadPlugin( loadPlugin(
activity, activity,
File(pluginData.filePath), File(pluginData.filePath),
@ -290,7 +289,7 @@ object PluginManager {
} }
} }
fun loadAllLocalPlugins(activity: Activity) = ioSafe { fun loadAllLocalPlugins(activity: Activity) {
val dir = File(LOCAL_PLUGINS_PATH) val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL) removeKey(PLUGINS_KEY_LOCAL)
@ -298,7 +297,7 @@ object PluginManager {
val res = dir.mkdirs() val res = dir.mkdirs()
if (!res) { if (!res) {
Log.w(TAG, "Failed to create local directories") Log.w(TAG, "Failed to create local directories")
return@ioSafe return
} }
} }

View file

@ -70,6 +70,28 @@ object RepositoryManager {
getKey("PREBUILT_REPOSITORIES") ?: emptyArray() getKey("PREBUILT_REPOSITORIES") ?: emptyArray()
} }
suspend fun parseRepoUrl(url: String): String? {
val fixedUrl = url.trim()
return if (fixedUrl.contains("^https?://".toRegex())) {
fixedUrl
} else if (fixedUrl.contains("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex())) {
fixedUrl.replace("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex(), "").let {
return@let if (!it.contains("^https?://".toRegex()))
"https://${it}"
else fixedUrl
}
} else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) {
suspendSafeApiCall {
app.get("https://l.cloudstream.cf/${fixedUrl}").let {
return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url
else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 ->
return@let2 if (it2.isSuccessful) it2.url else null
}
}
}
} else null
}
suspend fun parseRepository(url: String): Repository? { suspend fun parseRepository(url: String): Repository? {
return suspendSafeApiCall { return suspendSafeApiCall {
// Take manifestVersion and such into account later // Take manifestVersion and such into account later

View file

@ -150,7 +150,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
} else { } else {
ChromecastSubtitlesFragment.getCurrentSavedStyle().apply { ChromecastSubtitlesFragment.getCurrentSavedStyle().apply {
val font = TextTrackStyle() val font = TextTrackStyle()
font.fontFamily = fontFamily ?: "Google Sans" font.setFontFamily(fontFamily ?: "Google Sans")
fontGenericFamily?.let { fontGenericFamily?.let {
font.fontGenericFamily = it font.fontGenericFamily = it
} }
@ -183,7 +183,9 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl
?: remoteMediaClient?.currentItem?.media?.contentId) ?: remoteMediaClient?.currentItem?.media?.contentId)
val sortingMethods = items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" }.toTypedArray() val sortingMethods =
items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" }
.toTypedArray()
val sotringIndex = items.indexOfFirst { it.url == contentUrl } val sotringIndex = items.indexOfFirst { it.url == contentUrl }
val arrayAdapter = val arrayAdapter =
@ -358,10 +360,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
} }
} }
override fun onSessionConnected(castSession: CastSession?) { override fun onSessionConnected(castSession: CastSession) {
castSession?.let { super.onSessionConnected(castSession)
super.onSessionConnected(it)
}
remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject()) remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
} }
} }

View file

@ -23,6 +23,9 @@ import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.transition.ChangeBounds
import androidx.transition.TransitionManager
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
@ -346,7 +349,9 @@ class HomeFragment : Fragment() {
builder.setContentView(R.layout.home_select_mainpage) builder.setContentView(R.layout.home_select_mainpage)
builder.show() builder.show()
builder.let { dialog -> builder.let { dialog ->
val isMultiLang = getApiProviderLangSettings().size > 1 val isMultiLang = getApiProviderLangSettings().let { set ->
set.size > 1 || set.contains(AllLanguagesName)
}
//dialog.window?.setGravity(Gravity.BOTTOM) //dialog.window?.setGravity(Gravity.BOTTOM)
var currentApiName = selectedApiName var currentApiName = selectedApiName
@ -548,7 +553,9 @@ class HomeFragment : Fragment() {
is Resource.Success -> { is Resource.Success -> {
home_preview?.isVisible = true home_preview?.isVisible = true
(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply { (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply {
setItems(preview.value) if (!setItems(preview.value.second, preview.value.first)) {
home_preview_viewpager?.setCurrentItem(0, false)
}
// home_preview_viewpager?.setCurrentItem(1000, false) // home_preview_viewpager?.setCurrentItem(1000, false)
} }
@ -557,6 +564,10 @@ class HomeFragment : Fragment() {
//} //}
} }
else -> { else -> {
(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.setItems(
listOf(),
false
)
home_preview?.isVisible = false home_preview?.isVisible = false
context?.fixPaddingStatusbar(home_watch_holder) context?.fixPaddingStatusbar(home_watch_holder)
} }
@ -571,13 +582,26 @@ class HomeFragment : Fragment() {
} }
home_preview_viewpager?.apply { home_preview_viewpager?.apply {
setPageTransformer(false, HomeScrollTransformer()) setPageTransformer(HomeScrollTransformer())
adapter = HomeScrollAdapter { load -> val callback: OnPageChangeCallback = object : OnPageChangeCallback() {
load.apply { override fun onPageSelected(position: Int) {
home_preview_tags?.text = tags?.joinToString("") ?: "" (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply {
home_preview_tags?.isGone = tags.isNullOrEmpty() if (position >= itemCount - 1 && hasMoreItems) {
home_preview_image?.setImage(posterUrl, posterHeaders) hasMoreItems = false // dont make two requests
home_preview_title?.text = name homeViewModel.loadMoreHomeScrollResponses()
}
getItem(position)
?.apply {
home_preview_title_holder?.let { parent ->
TransitionManager.beginDelayedTransition(parent, ChangeBounds())
}
// home_preview_tags?.text = tags?.joinToString(" • ") ?: ""
// home_preview_tags?.isGone = tags.isNullOrEmpty()
// home_preview_image?.setImage(posterUrl, posterHeaders)
// home_preview_title?.text = name
home_preview_play?.setOnClickListener { home_preview_play?.setOnClickListener {
activity?.loadResult(url, apiName, START_ACTION_RESUME_LATEST) activity?.loadResult(url, apiName, START_ACTION_RESUME_LATEST)
//activity.loadSearchResult(url, START_ACTION_RESUME_LATEST) //activity.loadSearchResult(url, START_ACTION_RESUME_LATEST)
@ -587,7 +611,7 @@ class HomeFragment : Fragment() {
//activity.loadSearchResult(random) //activity.loadSearchResult(random)
} }
// very ugly code, but I dont care // very ugly code, but I dont care
val watchType = DataStoreHelper.getResultWatchState(load.getId()) val watchType = DataStoreHelper.getResultWatchState(this.getId())
home_preview_bookmark?.setText(watchType.stringRes) home_preview_bookmark?.setText(watchType.stringRes)
home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
null, null,
@ -597,29 +621,37 @@ class HomeFragment : Fragment() {
) )
home_preview_bookmark?.setOnClickListener { fab -> home_preview_bookmark?.setOnClickListener { fab ->
activity?.showBottomDialog( activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) } WatchType.values()
.map { fab.context.getString(it.stringRes) }
.toList(), .toList(),
DataStoreHelper.getResultWatchState(load.getId()).ordinal, DataStoreHelper.getResultWatchState(this.getId()).ordinal,
fab.context.getString(R.string.action_add_to_bookmarks), fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false, showApply = false,
{}) { {}) {
val newValue = WatchType.values()[it] val newValue = WatchType.values()[it]
home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
null, null,
getDrawable(home_preview_bookmark.context, newValue.iconRes), getDrawable(
home_preview_bookmark.context,
newValue.iconRes
),
null, null,
null null
) )
home_preview_bookmark?.setText(newValue.stringRes) home_preview_bookmark?.setText(newValue.stringRes)
updateWatchStatus(load, newValue) updateWatchStatus(this, newValue)
reloadStored() reloadStored()
} }
} }
}
} }
} }
}
}
registerOnPageChangeCallback(callback)
adapter = HomeScrollAdapter()
}
observe(homeViewModel.apiName) { apiName -> observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName currentApiName = apiName

View file

@ -1,60 +1,89 @@
package com.lagradost.cloudstream3.ui.home package com.lagradost.cloudstream3.ui.home
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import androidx.core.view.isGone
import androidx.viewpager.widget.PagerAdapter import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.home_scroll_view.view.*
class HomeScrollAdapter(private val onPrimaryCallback: (LoadResponse) -> Unit) : PagerAdapter() { class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var items: List<LoadResponse> = listOf() private var items: MutableList<LoadResponse> = mutableListOf()
var hasMoreItems: Boolean = false
fun setItems(newItems: List<LoadResponse>) { fun getItem(position: Int) : LoadResponse? {
items = newItems return items.getOrNull(position)
notifyDataSetChanged()
} }
override fun getCount(): Int { fun setItems(newItems: List<LoadResponse>, hasNext: Boolean): Boolean {
return Int.MAX_VALUE//items.size val isSame = newItems.firstOrNull()?.url == items.firstOrNull()?.url
hasMoreItems = hasNext
val diffResult = DiffUtil.calculateDiff(
HomeScrollDiffCallback(this.items, newItems)
)
items.clear()
items.addAll(newItems)
diffResult.dispatchUpdatesTo(this)
return isSame
} }
override fun getItemPosition(`object`: Any): Int { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return POSITION_NONE//super.getItemPosition(`object`) return CardViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.home_scroll_view, parent, false),
)
} }
private fun getItemAtPosition(idx: Int): LoadResponse { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
return items[idx % items.size] when (holder) {
} is CardViewHolder -> {
holder.bind(items[position])
override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) { }
super.setPrimaryItem(container, position, `object`) }
onPrimaryCallback.invoke(getItemAtPosition(position)) }
}
class CardViewHolder
override fun instantiateItem(container: ViewGroup, position: Int): Any { constructor(
val image = ImageView(container.context) itemView: View,
val item = getItemAtPosition(position) ) :
image.scaleType = ImageView.ScaleType.CENTER_CROP RecyclerView.ViewHolder(itemView) {
image.setImage(item.posterUrl ?: item.backgroundPosterUrl, item.posterHeaders)
fun bind(card: LoadResponse) {
// val itemView: View = mLayoutInflater.inflate(R.layout.pager_item, container, false) card.apply {
itemView.home_scroll_preview_tags?.text = tags?.joinToString("") ?: ""
// val imageView: ImageView = itemView.findViewById<View>(R.id.imageView) as ImageView itemView.home_scroll_preview_tags?.isGone = tags.isNullOrEmpty()
// imageView.setImageResource(mResources.get(position)) itemView.home_scroll_preview?.setImage(posterUrl, posterHeaders)
itemView.home_scroll_preview_title?.text = name
container.addView(image) }
}
return image }
}
class HomeScrollDiffCallback(
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { private val oldList: List<LoadResponse>,
container.removeView(`object` as View) private val newList: List<LoadResponse>
} ) :
DiffUtil.Callback() {
override fun isViewFromObject(view: View, `object`: Any): Boolean { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
return view === `object` oldList[oldItemPosition].url == newList[newItemPosition].url
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}
override fun getItemCount(): Int {
return items.size
} }
} }

View file

@ -1,13 +1,23 @@
package com.lagradost.cloudstream3.ui.home package com.lagradost.cloudstream3.ui.home
import android.view.View import android.view.View
import androidx.viewpager.widget.ViewPager import androidx.viewpager2.widget.ViewPager2
class HomeScrollTransformer : ViewPager.PageTransformer { class HomeScrollTransformer : ViewPager2.PageTransformer {
override fun transformPage(page: View, position: Float) { override fun transformPage(page: View, position: Float) {
//page.translationX = -position * page.width / 2.0f
//val params = RecyclerView.LayoutParams(
// RecyclerView.LayoutParams.MATCH_PARENT,
// 0
//)
//page.layoutParams = params
//progressBar?.layoutParams = params
val padding = (-position * page.width / 2).toInt()
page.setPadding( page.setPadding(
maxOf(0, (-position * page.width / 2).toInt()), 0, padding, 0,
maxOf(0, (position * page.width / 2).toInt()), 0 -padding, 0
) )
} }
} }

View file

@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
@ -12,17 +13,12 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
@ -35,7 +31,6 @@ import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
import kotlin.collections.set import kotlin.collections.set
@ -49,6 +44,8 @@ class HomeViewModel : ViewModel() {
private val _randomItems = MutableLiveData<List<SearchResponse>?>(null) private val _randomItems = MutableLiveData<List<SearchResponse>?>(null)
val randomItems: LiveData<List<SearchResponse>?> = _randomItems val randomItems: LiveData<List<SearchResponse>?> = _randomItems
private var currentShuffledList: List<SearchResponse> = listOf()
private fun autoloadRepo(): APIRepository { private fun autoloadRepo(): APIRepository {
return APIRepository(apis.first { it.hasMainPage }) return APIRepository(apis.first { it.hasMainPage })
} }
@ -61,9 +58,12 @@ class HomeViewModel : ViewModel() {
val bookmarks: LiveData<Pair<Boolean, List<SearchResponse>>> = _bookmarks val bookmarks: LiveData<Pair<Boolean, List<SearchResponse>>> = _bookmarks
private val _resumeWatching = MutableLiveData<List<SearchResponse>>() private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
private val _preview = MutableLiveData<Resource<List<LoadResponse>>>() private val _preview = MutableLiveData<Resource<Pair<Boolean, List<LoadResponse>>>>()
private val previewResponses = mutableListOf<LoadResponse>()
private val previewResponsesAdded = mutableSetOf<String>()
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
val preview: LiveData<Resource<List<LoadResponse>>> = _preview val preview: LiveData<Resource<Pair<Boolean, List<LoadResponse>>>> = _preview
fun loadResumeWatching() = viewModelScope.launchSafe { fun loadResumeWatching() = viewModelScope.launchSafe {
val resumeWatching = withContext(Dispatchers.IO) { val resumeWatching = withContext(Dispatchers.IO) {
@ -212,6 +212,40 @@ class HomeViewModel : ViewModel() {
expandAndReturn(name) expandAndReturn(name)
} }
// returns the amount of items added and modifies current
private suspend fun updatePreviewResponses(
current: MutableList<LoadResponse>,
alreadyAdded: MutableSet<String>,
shuffledList: List<SearchResponse>,
size: Int
): Int {
var count = 0
val addItems = arrayListOf<SearchResponse>()
for (searchResponse in shuffledList) {
if (!alreadyAdded.contains(searchResponse.url)) {
addItems.add(searchResponse)
previewResponsesAdded.add(searchResponse.url)
if (++count >= size) {
break
}
}
}
val add = addItems.amap { searchResponse ->
repo?.load(searchResponse.url)
}.mapNotNull { if (it != null && it is Resource.Success) it.value else null }
current.addAll(add)
return add.size
}
private var addJob: Job? = null
fun loadMoreHomeScrollResponses() {
addJob = ioSafe {
updatePreviewResponses(previewResponses, previewResponsesAdded, currentShuffledList, 1)
_preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses))
}
}
private fun load(api: MainAPI?) = ioSafe { private fun load(api: MainAPI?) = ioSafe {
repo = if (api != null) { repo = if (api != null) {
@ -226,6 +260,7 @@ class HomeViewModel : ViewModel() {
if (repo?.hasMainPage == true) { if (repo?.hasMainPage == true) {
_page.postValue(Resource.Loading()) _page.postValue(Resource.Loading())
_preview.postValue(Resource.Loading()) _preview.postValue(Resource.Loading())
addJob?.cancel()
when (val data = repo?.getMainPage(1, null)) { when (val data = repo?.getMainPage(1, null)) {
is Resource.Success -> { is Resource.Success -> {
@ -241,64 +276,10 @@ class HomeViewModel : ViewModel() {
} }
val items = data.value.mapNotNull { it?.items }.flatten() val items = data.value.mapNotNull { it?.items }.flatten()
val responses = ioWork {
items.flatMap { it.list }.shuffled().take(6).map { searchResponse ->
async { repo?.load(searchResponse.url) }
}.map { it.await() }.mapNotNull { if (it != null && it is Resource.Success) it.value else null } }
//.amap { searchResponse ->
// repo?.load(searchResponse.url)
///}
//.map { searchResponse ->
// async { repo?.load(searchResponse.url) }
// }.map { it.await() }
if (responses.isEmpty()) { previewResponses.clear()
_preview.postValue( previewResponsesAdded.clear()
Resource.Failure(
false,
null,
null,
"No homepage responses"
)
)
} else {
_preview.postValue(Resource.Success(responses))
}
/*
items.randomOrNull()?.list?.randomOrNull()?.url?.let { url ->
// backup request in case first fails
var first = repo?.load(url)
if(first == null ||first is Resource.Failure) {
first = repo?.load(items.random().list.random().url)
}
first?.let {
_preview.postValue(it)
} ?: run {
_preview.postValue(
Resource.Failure(
false,
null,
null,
"No repo found, this should never happen"
)
)
}
} ?: run {
_preview.postValue(
Resource.Failure(
false,
null,
null,
"No homepage items"
)
)
}*/
_page.postValue(Resource.Success(expandable))
//val home = data.value //val home = data.value
if (items.isNotEmpty()) { if (items.isNotEmpty()) {
@ -313,9 +294,30 @@ class HomeViewModel : ViewModel() {
context?.filterSearchResultByFilmQuality(currentList.shuffled()) context?.filterSearchResultByFilmQuality(currentList.shuffled())
?: currentList.shuffled() ?: currentList.shuffled()
updatePreviewResponses(
previewResponses,
previewResponsesAdded,
randomItems,
3
)
_randomItems.postValue(randomItems) _randomItems.postValue(randomItems)
currentShuffledList = randomItems
} }
} }
if (previewResponses.isEmpty()) {
_preview.postValue(
Resource.Failure(
false,
null,
null,
"No homepage responses"
)
)
} else {
_preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses))
}
_page.postValue(Resource.Success(expandable))
} catch (e: Exception) { } catch (e: Exception) {
_randomItems.postValue(emptyList()) _randomItems.postValue(emptyList())
logError(e) logError(e)

View file

@ -15,6 +15,7 @@ import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
import com.google.android.exoplayer2.source.* import com.google.android.exoplayer2.source.*
import com.google.android.exoplayer2.text.TextRenderer import com.google.android.exoplayer2.text.TextRenderer
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride
import com.google.android.exoplayer2.trackselection.TrackSelector import com.google.android.exoplayer2.trackselection.TrackSelector
import com.google.android.exoplayer2.ui.SubtitleView import com.google.android.exoplayer2.ui.SubtitleView
import com.google.android.exoplayer2.upstream.* import com.google.android.exoplayer2.upstream.*
@ -218,7 +219,43 @@ class CS3IPlayer : IPlayer {
var currentSubtitles: SubtitleData? = null var currentSubtitles: SubtitleData? = null
override fun setMaxVideoSize(width: Int, height: Int) { private fun List<Tracks.Group>.getTrack(id: String?): Pair<TrackGroup, Int>? {
if (id == null) return null
// This beast of an expression does:
// 1. Filter all audio tracks
// 2. Get all formats in said audio tacks
// 3. Gets all ids of the formats
// 4. Filters to find the first audio track with the same id as the audio track we are looking for
// 5. Returns the media group and the index of the audio track in the group
return this.firstNotNullOfOrNull { group ->
(0 until group.mediaTrackGroup.length).map {
group.getTrackFormat(it) to it
}.firstOrNull { it.first.id == id }
?.let { group.mediaTrackGroup to it.second }
}
}
override fun setMaxVideoSize(width: Int, height: Int, id: String?) {
if (id != null) {
val videoTrack =
exoPlayer?.currentTracks?.groups?.filter { it.type == TRACK_TYPE_VIDEO }
?.getTrack(id)
if (videoTrack != null) {
exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters
?.buildUpon()
?.setOverrideForType(
TrackSelectionOverride(
videoTrack.first,
videoTrack.second
)
)
?.build()
?: return
return
}
}
exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters
?.buildUpon() ?.buildUpon()
?.setMaxVideoSize(width, height) ?.setMaxVideoSize(width, height)
@ -226,8 +263,29 @@ class CS3IPlayer : IPlayer {
?: return ?: return
} }
override fun setPreferredAudioTrack(trackLanguage: String?) { override fun setPreferredAudioTrack(trackLanguage: String?, id: String?) {
preferredAudioTrackLanguage = trackLanguage preferredAudioTrackLanguage = trackLanguage
if (id != null) {
val audioTrack =
exoPlayer?.currentTracks?.groups?.filter { it.type == TRACK_TYPE_AUDIO }
?.getTrack(id)
if (audioTrack != null) {
exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters
?.buildUpon()
?.setOverrideForType(
TrackSelectionOverride(
audioTrack.first,
audioTrack.second
)
)
?.build()
?: return
return
}
}
exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters exoPlayer?.trackSelectionParameters = exoPlayer?.trackSelectionParameters
?.buildUpon() ?.buildUpon()
?.setPreferredAudioLanguage(trackLanguage) ?.setPreferredAudioLanguage(trackLanguage)
@ -239,11 +297,11 @@ class CS3IPlayer : IPlayer {
/** /**
* Gets all supported formats in a list * Gets all supported formats in a list
* */ * */
private fun List<TracksInfo.TrackGroupInfo>.getFormats(): List<Format> { private fun List<Tracks.Group>.getFormats(): List<Pair<Format, Int>> {
return this.map { return this.map {
(0 until it.trackGroup.length).mapNotNull { i -> (0 until it.mediaTrackGroup.length).mapNotNull { i ->
if (it.isSupported) if (it.isSupported)
it.trackGroup.getFormat(i) // to it.isSelected it.mediaTrackGroup.getFormat(i) to i
else null else null
} }
}.flatten() }.flatten()
@ -270,11 +328,12 @@ class CS3IPlayer : IPlayer {
} }
override fun getVideoTracks(): CurrentTracks { override fun getVideoTracks(): CurrentTracks {
val allTracks = exoPlayer?.currentTracksInfo?.trackGroupInfos ?: emptyList() val allTracks = exoPlayer?.currentTracks?.groups ?: emptyList()
val videoTracks = allTracks.filter { it.trackType == TRACK_TYPE_VIDEO }.getFormats() val videoTracks = allTracks.filter { it.type == TRACK_TYPE_VIDEO }
.map { it.toVideoTrack() } .getFormats()
val audioTracks = allTracks.filter { it.trackType == TRACK_TYPE_AUDIO }.getFormats() .map { it.first.toVideoTrack() }
.map { it.toAudioTrack() } val audioTracks = allTracks.filter { it.type == TRACK_TYPE_AUDIO }.getFormats()
.map { it.first.toAudioTrack() }
return CurrentTracks( return CurrentTracks(
exoPlayer?.videoFormat?.toVideoTrack(), exoPlayer?.videoFormat?.toVideoTrack(),
@ -611,7 +670,12 @@ class CS3IPlayer : IPlayer {
} else it } else it
}.toTypedArray() }.toTypedArray()
} }
.setTrackSelector(trackSelector ?: getTrackSelector(context, maxVideoHeight)) .setTrackSelector(
trackSelector ?: getTrackSelector(
context,
maxVideoHeight
)
)
.setLoadControl( .setLoadControl(
DefaultLoadControl.Builder() DefaultLoadControl.Builder()
.setTargetBufferBytes( .setTargetBufferBytes(
@ -781,10 +845,7 @@ class CS3IPlayer : IPlayer {
isPlaying = exo.isPlaying isPlaying = exo.isPlaying
} }
exoPlayer?.addListener(object : Player.Listener { exoPlayer?.addListener(object : Player.Listener {
/** override fun onTracksChanged(tracks: Tracks) {
* Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection.
* */
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
fun Format.isSubtitle(): Boolean { fun Format.isSubtitle(): Boolean {
return this.sampleMimeType?.contains("video/") == false && return this.sampleMimeType?.contains("video/") == false &&
this.sampleMimeType?.contains("audio/") == false this.sampleMimeType?.contains("audio/") == false
@ -792,17 +853,17 @@ class CS3IPlayer : IPlayer {
normalSafeApiCall { normalSafeApiCall {
exoPlayerSelectedTracks = exoPlayerSelectedTracks =
tracksInfo.trackGroupInfos.mapNotNull { tracks.groups.mapNotNull {
val format = it.trackGroup.getFormat(0) val format = it.mediaTrackGroup.getFormat(0)
if (format.isSubtitle()) if (format.isSubtitle())
format.language?.let { lang -> lang to it.isSelected } format.language?.let { lang -> lang to it.isSelected }
else null else null
} }
val exoPlayerReportedTracks = tracksInfo.trackGroupInfos.mapNotNull { val exoPlayerReportedTracks = tracks.groups.mapNotNull {
// Filter out unsupported tracks // Filter out unsupported tracks
if (it.isSupported) if (it.isSupported)
it.trackGroup.getFormat(0) it.mediaTrackGroup.getFormat(0)
else else
null null
}.mapNotNull { }.mapNotNull {
@ -827,7 +888,6 @@ class CS3IPlayer : IPlayer {
onTracksInfoChanged?.invoke() onTracksInfoChanged?.invoke()
subtitlesUpdates?.invoke() subtitlesUpdates?.invoke()
} }
super.onTracksInfoChanged(tracksInfo)
} }
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {

View file

@ -796,15 +796,16 @@ class GeneratorPlayer : FullScreenPlayer() {
} }
tracksDialog.apply_btt?.setOnClickListener { tracksDialog.apply_btt?.setOnClickListener {
val currentTrack = currentAudioTracks.getOrNull(audioIndexStart)
player.setPreferredAudioTrack( player.setPreferredAudioTrack(
currentAudioTracks.getOrNull(audioIndexStart)?.language currentTrack?.language, currentTrack?.id
) )
val currentVideo = currentVideoTracks.getOrNull(videoIndex) val currentVideo = currentVideoTracks.getOrNull(videoIndex)
val width = currentVideo?.width ?: NO_VALUE val width = currentVideo?.width ?: NO_VALUE
val height = currentVideo?.height ?: NO_VALUE val height = currentVideo?.height ?: NO_VALUE
if (width != NO_VALUE && height != NO_VALUE) { if (width != NO_VALUE && height != NO_VALUE) {
player.setMaxVideoSize(width, height) player.setMaxVideoSize(width, height, currentVideo?.id)
} }
tracksDialog.dismissSafe(activity) tracksDialog.dismissSafe(activity)

View file

@ -161,9 +161,9 @@ interface IPlayer {
fun getVideoTracks(): CurrentTracks fun getVideoTracks(): CurrentTracks
/** If no parameters are set it'll default to no set size */ /** If no parameters are set it'll default to no set size, Specifying the id allows for track overrides to force the player to pick the quality. */
fun setMaxVideoSize(width: Int = Int.MAX_VALUE, height: Int = Int.MAX_VALUE) fun setMaxVideoSize(width: Int = Int.MAX_VALUE, height: Int = Int.MAX_VALUE, id: String? = null)
/** If no trackLanguage is set it'll default to first track */ /** If no trackLanguage is set it'll default to first track. Specifying the id allows for track overrides as the language can be identical. */
fun setPreferredAudioTrack(trackLanguage: String?) fun setPreferredAudioTrack(trackLanguage: String?, id: String? = null)
} }

View file

@ -0,0 +1,423 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lagradost.cloudstream3.ui.player;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.CueGroup;
import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleDecoderFactory;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;
// DO NOT CONVERT TO KOTLIN AUTOMATICALLY, IT FUCKS UP AND DOES NOT DISPLAY SUBS FOR SOME REASON
/**
* A renderer for text.
*
* <p>{@link Subtitle}s are decoded from sample data using {@link SubtitleDecoder} instances
* obtained from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s
* is delegated to a {@link TextOutput}.
*/
public class NonFinalTextRenderer extends BaseRenderer implements Callback {
private static final String TAG = "TextRenderer";
/**
* @param trackType The track type that the renderer handles. One of the {@link C} {@code
* TRACK_TYPE_*} constants.
* @param outputHandler
*/
public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) {
super(trackType);
this.outputHandler = outputHandler;
}
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
REPLACEMENT_STATE_NONE,
REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
REPLACEMENT_STATE_WAIT_END_OF_STREAM
})
private @interface ReplacementState {}
/** The decoder does not need to be replaced. */
private static final int REPLACEMENT_STATE_NONE = 0;
/**
* The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing
* decoder. We need to do so in order to ensure that it outputs any remaining buffers before we
* release it.
*/
private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1;
/**
* The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder.
* We're waiting for the decoder to output an end of stream signal to indicate that it has output
* any remaining buffers before we release it.
*/
private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2;
private static final int MSG_UPDATE_OUTPUT = 0;
@Nullable private final Handler outputHandler;
private TextOutput output = null;
private SubtitleDecoderFactory decoderFactory = null;
private FormatHolder formatHolder = null;
private boolean inputStreamEnded;
private boolean outputStreamEnded;
private boolean waitingForKeyFrame;
private @ReplacementState int decoderReplacementState;
@Nullable private Format streamFormat;
@Nullable private SubtitleDecoder decoder;
@Nullable private SubtitleInputBuffer nextInputBuffer;
@Nullable private SubtitleOutputBuffer subtitle;
@Nullable private SubtitleOutputBuffer nextSubtitle;
private int nextSubtitleEventIndex;
private long finalStreamEndPositionUs;
/**
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using {@link
* android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
* directly on the player's internal rendering thread.
*/
public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) {
this(output, outputLooper, SubtitleDecoderFactory.DEFAULT);
}
/**
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using {@link
* android.app.Activity#getMainLooper()}. Null may be passed if the output should be called
* directly on the player's internal rendering thread.
* @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances.
*/
public NonFinalTextRenderer(
TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) {
super(C.TRACK_TYPE_TEXT);
this.output = checkNotNull(output);
this.outputHandler =
outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this);
this.decoderFactory = decoderFactory;
formatHolder = new FormatHolder();
finalStreamEndPositionUs = C.TIME_UNSET;
}
@Override
public String getName() {
return TAG;
}
@Override
public @Capabilities int supportsFormat(Format format) {
if (decoderFactory.supportsFormat(format)) {
return RendererCapabilities.create(
format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM);
} else if (MimeTypes.isText(format.sampleMimeType)) {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE);
} else {
return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE);
}
}
/**
* Sets the position at which to stop rendering the current stream.
*
* <p>Must be called after {@link #setCurrentStreamFinal()}.
*
* @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to
* render until the end of the current stream.
*/
// TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded
// on the loading side of SampleQueue.
public void setFinalStreamEndPositionUs(long streamEndPositionUs) {
checkState(isCurrentStreamFinal());
this.finalStreamEndPositionUs = streamEndPositionUs;
}
@Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) {
streamFormat = formats[0];
if (decoder != null) {
decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM;
} else {
initDecoder();
}
}
@Override
protected void onPositionReset(long positionUs, boolean joining) {
clearOutput();
inputStreamEnded = false;
outputStreamEnded = false;
finalStreamEndPositionUs = C.TIME_UNSET;
if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
replaceDecoder();
} else {
releaseBuffers();
checkNotNull(decoder).flush();
}
}
@Override
public void render(long positionUs, long elapsedRealtimeUs) {
if (isCurrentStreamFinal()
&& finalStreamEndPositionUs != C.TIME_UNSET
&& positionUs >= finalStreamEndPositionUs) {
releaseBuffers();
outputStreamEnded = true;
}
if (outputStreamEnded) {
return;
}
if (nextSubtitle == null) {
checkNotNull(decoder).setPositionUs(positionUs);
try {
nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer();
} catch (SubtitleDecoderException e) {
handleDecoderError(e);
return;
}
}
if (getState() != STATE_STARTED) {
return;
}
boolean textRendererNeedsUpdate = false;
if (subtitle != null) {
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
// advance to the next event.
long subtitleNextEventTimeUs = getNextEventTime();
while (subtitleNextEventTimeUs <= positionUs) {
nextSubtitleEventIndex++;
subtitleNextEventTimeUs = getNextEventTime();
textRendererNeedsUpdate = true;
}
}
if (nextSubtitle != null) {
SubtitleOutputBuffer nextSubtitle = this.nextSubtitle;
if (nextSubtitle.isEndOfStream()) {
if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) {
if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
replaceDecoder();
} else {
releaseBuffers();
outputStreamEnded = true;
}
}
} else if (nextSubtitle.timeUs <= positionUs) {
// Advance to the next subtitle. Sync the next event index and trigger an update.
if (subtitle != null) {
subtitle.release();
}
nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs);
subtitle = nextSubtitle;
this.nextSubtitle = null;
textRendererNeedsUpdate = true;
}
}
if (textRendererNeedsUpdate) {
// If textRendererNeedsUpdate then subtitle must be non-null.
checkNotNull(subtitle);
// textRendererNeedsUpdate is set and we're playing. Update the renderer.
updateOutput(subtitle.getCues(positionUs));
}
if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
return;
}
try {
while (!inputStreamEnded) {
@Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer;
if (nextInputBuffer == null) {
nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer();
if (nextInputBuffer == null) {
return;
}
this.nextInputBuffer = nextInputBuffer;
}
if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) {
nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
this.nextInputBuffer = null;
decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM;
return;
}
// Try and read the next subtitle from the source.
@ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0);
if (result == C.RESULT_BUFFER_READ) {
if (nextInputBuffer.isEndOfStream()) {
inputStreamEnded = true;
waitingForKeyFrame = false;
} else {
@Nullable Format format = formatHolder.format;
if (format == null) {
// We haven't received a format yet.
return;
}
nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs;
nextInputBuffer.flip();
waitingForKeyFrame &= !nextInputBuffer.isKeyFrame();
}
if (!waitingForKeyFrame) {
checkNotNull(decoder).queueInputBuffer(nextInputBuffer);
this.nextInputBuffer = null;
}
} else if (result == C.RESULT_NOTHING_READ) {
return;
}
}
} catch (SubtitleDecoderException e) {
handleDecoderError(e);
}
}
@Override
protected void onDisabled() {
streamFormat = null;
finalStreamEndPositionUs = C.TIME_UNSET;
clearOutput();
releaseDecoder();
}
@Override
public boolean isEnded() {
return outputStreamEnded;
}
@Override
public boolean isReady() {
// Don't block playback whilst subtitles are loading.
// Note: To change this behavior, it will be necessary to consider [Internal: b/12949941].
return true;
}
private void releaseBuffers() {
nextInputBuffer = null;
nextSubtitleEventIndex = C.INDEX_UNSET;
if (subtitle != null) {
subtitle.release();
subtitle = null;
}
if (nextSubtitle != null) {
nextSubtitle.release();
nextSubtitle = null;
}
}
private void releaseDecoder() {
releaseBuffers();
checkNotNull(decoder).release();
decoder = null;
decoderReplacementState = REPLACEMENT_STATE_NONE;
}
private void initDecoder() {
waitingForKeyFrame = true;
decoder = decoderFactory.createDecoder(checkNotNull(streamFormat));
}
private void replaceDecoder() {
releaseDecoder();
initDecoder();
}
private long getNextEventTime() {
if (nextSubtitleEventIndex == C.INDEX_UNSET) {
return Long.MAX_VALUE;
}
checkNotNull(subtitle);
return nextSubtitleEventIndex >= subtitle.getEventTimeCount()
? Long.MAX_VALUE
: subtitle.getEventTime(nextSubtitleEventIndex);
}
private void updateOutput(List<Cue> cues) {
if (outputHandler != null) {
outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget();
} else {
invokeUpdateOutputInternal(cues);
}
}
private void clearOutput() {
updateOutput(Collections.emptyList());
}
@SuppressWarnings("unchecked")
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_OUTPUT:
invokeUpdateOutputInternal((List<Cue>) msg.obj);
return true;
default:
throw new IllegalStateException();
}
}
private void invokeUpdateOutputInternal(List<Cue> cues) {
output.onCues(cues);
output.onCues(new CueGroup(cues));
}
/**
* Called when {@link #decoder} throws an exception, so it can be logged and playback can
* continue.
*
* <p>Logs {@code e} and resets state to allow decoding the next sample.
*/
private void handleDecoderError(SubtitleDecoderException e) {
Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e);
clearOutput();
replaceDecoder();
}
}

View file

@ -1,382 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.lagradost.cloudstream3.ui.player
import android.os.Handler
import android.os.Looper
import android.os.Message
import androidx.annotation.IntDef
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.SampleStream.ReadDataResult
import com.google.android.exoplayer2.text.*
import com.google.android.exoplayer2.text.Cue.DIMEN_UNSET
import com.google.android.exoplayer2.text.Cue.LINE_TYPE_NUMBER
import com.google.android.exoplayer2.util.Assertions
import com.google.android.exoplayer2.util.Log
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.exoplayer2.util.Util
/**
* A renderer for text.
*
*
* [Subtitle]s are decoded from sample data using [SubtitleDecoder] instances
* obtained from a [SubtitleDecoderFactory]. The actual rendering of the subtitle [Cue]s
* is delegated to a [TextOutput].
*/
open class NonFinalTextRenderer @JvmOverloads constructor(
output: TextOutput?,
outputLooper: Looper?,
private val decoderFactory: SubtitleDecoderFactory = SubtitleDecoderFactory.DEFAULT
) :
BaseRenderer(C.TRACK_TYPE_TEXT), Handler.Callback {
@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@IntDef(
REPLACEMENT_STATE_NONE,
REPLACEMENT_STATE_SIGNAL_END_OF_STREAM,
REPLACEMENT_STATE_WAIT_END_OF_STREAM
)
private annotation class ReplacementState
private val outputHandler: Handler? = if (outputLooper == null) null else Util.createHandler(
outputLooper, /* callback= */
this
)
private val output: TextOutput = Assertions.checkNotNull(output)
private val formatHold: FormatHolder = FormatHolder()
private var inputStreamEnded = false
private var outputStreamEnded = false
private var waitingForKeyFrame = false
@ReplacementState
private var decoderReplacementState = 0
private var streamFormat: Format? = null
private var decoder: SubtitleDecoder? = null
private var nextInputBuffer: SubtitleInputBuffer? = null
private var subtitle: SubtitleOutputBuffer? = null
private var nextSubtitle: SubtitleOutputBuffer? = null
private var nextSubtitleEventIndex = 0
private var finalStreamEndPositionUs: Long
override fun getName(): String {
return TAG
}
@RendererCapabilities.Capabilities
override fun supportsFormat(format: Format): Int {
return if (decoderFactory.supportsFormat(format)) {
RendererCapabilities.create(
if (format.cryptoType == C.CRYPTO_TYPE_NONE) C.FORMAT_HANDLED else C.FORMAT_UNSUPPORTED_DRM
)
} else if (MimeTypes.isText(format.sampleMimeType)) {
RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE)
} else {
RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE)
}
}
/**
* Sets the position at which to stop rendering the current stream.
*
*
* Must be called after [.setCurrentStreamFinal].
*
* @param streamEndPositionUs The position to stop rendering at or [C.LENGTH_UNSET] to
* render until the end of the current stream.
*/
override fun onStreamChanged(formats: Array<Format>, startPositionUs: Long, offsetUs: Long) {
streamFormat = formats[0]
if (decoder != null) {
decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM
} else {
initDecoder()
}
}
override fun onPositionReset(positionUs: Long, joining: Boolean) {
clearOutput()
inputStreamEnded = false
outputStreamEnded = false
finalStreamEndPositionUs = C.TIME_UNSET
if (decoderReplacementState != REPLACEMENT_STATE_NONE) {
replaceDecoder()
} else {
releaseBuffers()
Assertions.checkNotNull(decoder).flush()
}
}
override fun render(positionUs: Long, elapsedRealtimeUs: Long) {
if (isCurrentStreamFinal
&& finalStreamEndPositionUs != C.TIME_UNSET && positionUs >= finalStreamEndPositionUs
) {
releaseBuffers()
outputStreamEnded = true
}
if (outputStreamEnded) {
return
}
if (nextSubtitle == null) {
Assertions.checkNotNull(decoder).setPositionUs(positionUs)
nextSubtitle = try {
Assertions.checkNotNull(decoder).dequeueOutputBuffer()
} catch (e: SubtitleDecoderException) {
handleDecoderError(e)
return
}
}
if (state != STATE_STARTED) {
return
}
var textRendererNeedsUpdate = false
if (subtitle != null) {
// We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
// advance to the next event.
var subtitleNextEventTimeUs = nextEventTime
while (subtitleNextEventTimeUs <= positionUs) {
nextSubtitleEventIndex++
subtitleNextEventTimeUs = nextEventTime
textRendererNeedsUpdate = true
}
}
if (nextSubtitle != null) {
val nextSubtitle = nextSubtitle
if (nextSubtitle!!.isEndOfStream) {
if (!textRendererNeedsUpdate && nextEventTime == Long.MAX_VALUE) {
if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
replaceDecoder()
} else {
releaseBuffers()
outputStreamEnded = true
}
}
} else if (nextSubtitle.timeUs <= positionUs) {
// Advance to the next subtitle. Sync the next event index and trigger an update.
if (subtitle != null) {
subtitle!!.release()
}
nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs)
subtitle = nextSubtitle
this.nextSubtitle = null
textRendererNeedsUpdate = true
}
}
if (textRendererNeedsUpdate) {
// If textRendererNeedsUpdate then subtitle must be non-null.
Assertions.checkNotNull(subtitle)
// textRendererNeedsUpdate is set and we're playing. Update the renderer.
updateOutput(subtitle!!.getCues(positionUs))
}
if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) {
return
}
try {
while (!inputStreamEnded) {
var nextInputBuffer = nextInputBuffer
if (nextInputBuffer == null) {
nextInputBuffer = Assertions.checkNotNull(decoder).dequeueInputBuffer()
if (nextInputBuffer == null) {
return
}
this.nextInputBuffer = nextInputBuffer
}
if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) {
nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM)
Assertions.checkNotNull(decoder).queueInputBuffer(nextInputBuffer)
this.nextInputBuffer = null
decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM
return
}
// Try and read the next subtitle from the source.
@ReadDataResult val result =
readSource(formatHold, nextInputBuffer, /* readFlags= */0)
if (result == C.RESULT_BUFFER_READ) {
if (nextInputBuffer.isEndOfStream) {
inputStreamEnded = true
waitingForKeyFrame = false
} else {
val format = formatHold.format
?: // We haven't received a format yet.
return
nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs
nextInputBuffer.flip()
waitingForKeyFrame = waitingForKeyFrame and !nextInputBuffer.isKeyFrame
}
if (!waitingForKeyFrame) {
Assertions.checkNotNull(decoder).queueInputBuffer(nextInputBuffer)
this.nextInputBuffer = null
}
} else if (result == C.RESULT_NOTHING_READ) {
return
}
}
} catch (e: SubtitleDecoderException) {
handleDecoderError(e)
}
}
override fun onDisabled() {
streamFormat = null
finalStreamEndPositionUs = C.TIME_UNSET
clearOutput()
releaseDecoder()
}
override fun isEnded(): Boolean {
return outputStreamEnded
}
override fun isReady(): Boolean {
// Don't block playback whilst subtitles are loading.
// Note: To change this behavior, it will be necessary to consider [Internal: b/12949941].
return true
}
private fun releaseBuffers() {
nextInputBuffer = null
nextSubtitleEventIndex = C.INDEX_UNSET
if (subtitle != null) {
subtitle!!.release()
subtitle = null
}
if (nextSubtitle != null) {
nextSubtitle!!.release()
nextSubtitle = null
}
}
private fun releaseDecoder() {
releaseBuffers()
Assertions.checkNotNull(decoder).release()
decoder = null
decoderReplacementState = REPLACEMENT_STATE_NONE
}
private fun initDecoder() {
waitingForKeyFrame = true
decoder = decoderFactory.createDecoder(Assertions.checkNotNull(streamFormat))
}
private fun replaceDecoder() {
releaseDecoder()
initDecoder()
}
private val nextEventTime: Long
get() {
if (nextSubtitleEventIndex == C.INDEX_UNSET) {
return Long.MAX_VALUE
}
Assertions.checkNotNull(subtitle)
return if (nextSubtitleEventIndex >= subtitle!!.eventTimeCount) Long.MAX_VALUE else subtitle!!.getEventTime(
nextSubtitleEventIndex
)
}
private fun updateOutput(cues: List<Cue>) {
if (outputHandler != null) {
outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget()
} else {
invokeUpdateOutputInternal(cues)
}
}
private fun clearOutput() {
updateOutput(emptyList())
}
override fun handleMessage(msg: Message): Boolean {
return when (msg.what) {
MSG_UPDATE_OUTPUT -> {
invokeUpdateOutputInternal(msg.obj as List<Cue>)
true
}
else -> throw IllegalStateException()
}
}
private fun invokeUpdateOutputInternal(cues: List<Cue>) {
output.onCues(cues.map { cue ->
val builder = cue.buildUpon()
// See https://github.com/google/ExoPlayer/issues/7934
// SubripDecoder texts tend to be DIMEN_UNSET which pushes up the
// subs unlike WEBVTT which creates an inconsistency
if (cue.line == DIMEN_UNSET)
builder.setLine(-1f, LINE_TYPE_NUMBER)
// this fixes https://github.com/LagradOst/CloudStream-3/issues/717
builder.setSize(DIMEN_UNSET).build()
})
}
/**
* Called when [.decoder] throws an exception, so it can be logged and playback can
* continue.
*
*
* Logs `e` and resets state to allow decoding the next sample.
*/
private fun handleDecoderError(e: SubtitleDecoderException) {
Log.e(
TAG,
"Subtitle decoding failed. streamFormat=$streamFormat", e
)
clearOutput()
replaceDecoder()
}
companion object {
private const val TAG = "TextRenderer"
/** The decoder does not need to be replaced. */
private const val REPLACEMENT_STATE_NONE = 0
/**
* The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing
* decoder. We need to do so in order to ensure that it outputs any remaining buffers before we
* release it.
*/
private const val REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1
/**
* The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder.
* We're waiting for the decoder to output an end of stream signal to indicate that it has output
* any remaining buffers before we release it.
*/
private const val REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2
private const val MSG_UPDATE_OUTPUT = 0
}
/**
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using [ ][android.app.Activity.getMainLooper]. Null may be passed if the output should be called
* directly on the player's internal rendering thread.
* @param decoderFactory A factory from which to obtain [SubtitleDecoder] instances.
*/
/**
* @param output The output.
* @param outputLooper The looper associated with the thread on which the output should be called.
* If the output makes use of standard Android UI components, then this should normally be the
* looper associated with the application's main thread, which can be obtained using [ ][android.app.Activity.getMainLooper]. Null may be passed if the output should be called
* directly on the player's internal rendering thread.
*/
init {
finalStreamEndPositionUs = C.TIME_UNSET
}
}

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.discord.panels.OverlappingPanelsLayout import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
@ -96,11 +97,10 @@ import kotlinx.android.synthetic.main.fragment_result.result_vpn
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.* import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.result_sync.* import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import com.google.android.material.chip.ChipDrawable
const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_RESUME_LATEST = 1
const val START_ACTION_LOAD_EP = 2 const val START_ACTION_LOAD_EP = 2
@ -839,6 +839,8 @@ open class ResultFragment : ResultTrailerPlayer() {
result_next_airing.setText(d.nextAiringEpisode) result_next_airing.setText(d.nextAiringEpisode)
result_next_airing_time.setText(d.nextAiringDate) result_next_airing_time.setText(d.nextAiringDate)
result_poster.setImage(d.posterImage) result_poster.setImage(d.posterImage)
result_poster_background.setImage(d.posterBackgroundImage)
//result_trailer_thumbnail.setImage(d.posterBackgroundImage, fadeIn = false)
if (d.posterImage != null && !isTrueTvSettings()) if (d.posterImage != null && !isTrueTvSettings())
result_poster_holder?.setOnClickListener { result_poster_holder?.setOnClickListener {

View file

@ -5,6 +5,9 @@ import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.DecelerateInterpolator
import android.widget.Toast import android.widget.Toast
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -20,27 +23,33 @@ import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.mvvm.Some import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result.result_cast_items
import kotlinx.android.synthetic.main.fragment_result.result_episodes_text
import kotlinx.android.synthetic.main.fragment_result.result_resume_parent
import kotlinx.android.synthetic.main.fragment_result.result_scroll
import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_swipe.result_back
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.* import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_recommendations.* import kotlinx.android.synthetic.main.result_recommendations.*
import kotlinx.android.synthetic.main.result_recommendations.result_recommendations
import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.android.synthetic.main.trailer_custom_layout.*
class ResultFragmentPhone : ResultFragment() { class ResultFragmentPhone : ResultFragment() {
var currentTrailers: List<ExtractorLink> = emptyList() var currentTrailers: List<ExtractorLink> = emptyList()
var currentTrailerIndex = 0 var currentTrailerIndex = 0
@ -84,8 +93,36 @@ class ResultFragmentPhone : ResultFragment() {
} ?: run { } ?: run {
false false
} }
//result_trailer_thumbnail?.setImageBitmap(result_poster_background?.drawable?.toBitmap())
result_trailer_loading?.isVisible = isSuccess result_trailer_loading?.isVisible = isSuccess
result_smallscreen_holder?.isVisible = !isSuccess && !isFullScreenPlayer val turnVis = !isSuccess && !isFullScreenPlayer
result_smallscreen_holder?.isVisible = turnVis
result_poster_background_holder?.apply {
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
interpolator = DecelerateInterpolator()
duration = 200
fillAfter = true
}
clearAnimation()
startAnimation(fadeIn)
}
//player_view?.apply {
//alpha = 0.0f
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
// duration = 200
// start()
//}
//val fadeIn: Animation = AlphaAnimation(0.0f, 1f).apply {
// interpolator = DecelerateInterpolator()
// duration = 2000
// fillAfter = true
//}
//startAnimation(fadeIn)
// }
// We don't want the trailer to be focusable if it's not visible // We don't want the trailer to be focusable if it's not visible
result_smallscreen_holder?.descendantFocusability = if (isSuccess) { result_smallscreen_holder?.descendantFocusability = if (isSuccess) {
@ -301,7 +338,8 @@ class ResultFragmentPhone : ResultFragment() {
observe(viewModel.selectedSeason) { text -> observe(viewModel.selectedSeason) { text ->
result_season_button.setText(text) result_season_button.setText(text)
selectSeason = (if (text is Some.Success) text.value else null)?.asStringNull(result_season_button?.context) selectSeason =
(if (text is Some.Success) text.value else null)?.asStringNull(result_season_button?.context)
// If the season button is visible the result season button will be next focus down // If the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true) if (result_season_button?.isVisible == true)
if (result_resume_parent?.isVisible == true) if (result_resume_parent?.isVisible == true)
@ -378,12 +416,16 @@ class ResultFragmentPhone : ResultFragment() {
r to (text?.asStringNull(ctx) ?: return@mapNotNull null) r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
} }
activity?.showDialog(names.map { it.second },names.indexOfFirst { it.second == selectSeason },"",false,{}) { itemId-> activity?.showDialog(
names.map { it.second },
names.indexOfFirst { it.second == selectSeason },
"",
false,
{}) { itemId ->
viewModel.changeSeason(names[itemId].first) viewModel.changeSeason(names[itemId].first)
} }
//view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) -> //view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
// index to name // index to name
//}) { //}) {

View file

@ -13,7 +13,9 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.IOnBackPressed
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.* import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.trailer_custom_layout.* import kotlinx.android.synthetic.main.trailer_custom_layout.*

View file

@ -88,6 +88,7 @@ data class ResultData(
var syncData: Map<String, String>, var syncData: Map<String, String>,
val posterImage: UiImage?, val posterImage: UiImage?,
val posterBackgroundImage: UiImage?,
val plotText: UiText, val plotText: UiText,
val apiName: UiText, val apiName: UiText,
val ratingText: UiText?, val ratingText: UiText?,
@ -170,6 +171,9 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
posterImage = img( posterImage = img(
posterUrl, posterHeaders posterUrl, posterHeaders
) ?: img(R.drawable.default_cover), ) ?: img(R.drawable.default_cover),
posterBackgroundImage = img(
backgroundPosterUrl ?: posterUrl, posterHeaders
) ?: img(R.drawable.default_cover),
titleText = txt(name), titleText = txt(name),
url = url, url = url,
tags = tags ?: emptyList(), tags = tags ?: emptyList(),

View file

@ -70,9 +70,9 @@ sealed class UiImage {
data class Drawable(@DrawableRes val resId: Int) : UiImage() data class Drawable(@DrawableRes val resId: Int) : UiImage()
} }
fun ImageView?.setImage(value: UiImage?) { fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) {
when (value) { when (value) {
is UiImage.Image -> setImageImage(value) is UiImage.Image -> setImageImage(value,fadeIn)
is UiImage.Drawable -> setImageDrawable(value) is UiImage.Drawable -> setImageDrawable(value)
null -> { null -> {
this?.isVisible = false this?.isVisible = false
@ -80,9 +80,9 @@ fun ImageView?.setImage(value: UiImage?) {
} }
} }
fun ImageView?.setImageImage(value: UiImage.Image) { fun ImageView?.setImageImage(value: UiImage.Image, fadeIn: Boolean = true) {
if (this == null) return if (this == null) return
this.isVisible = setImage(value.url, value.headers, value.errorDrawable) this.isVisible = setImage(value.url, value.headers, value.errorDrawable, fadeIn)
} }
fun ImageView?.setImageDrawable(value: UiImage.Drawable) { fun ImageView?.setImageDrawable(value: UiImage.Drawable) {

View file

@ -221,7 +221,9 @@ class SearchFragment : Fragment() {
builder.setContentView(R.layout.home_select_mainpage) builder.setContentView(R.layout.home_select_mainpage)
builder.show() builder.show()
builder.let { dialog -> builder.let { dialog ->
val isMultiLang = ctx.getApiProviderLangSettings().size > 1 val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
set.size > 1 || set.contains(AllLanguagesName)
}
val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt) val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt)
val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt) val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt)

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.settings package com.lagradost.cloudstream3.ui.settings
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
@ -9,17 +8,15 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import kotlin.reflect.jvm.internal.impl.descriptors.deserialization.PlatformDependentDeclarationFilter.All
class SettingsProviders : PreferenceFragmentCompat() { class SettingsProviders : PreferenceFragmentCompat() {
@ -63,13 +60,17 @@ class SettingsProviders : PreferenceFragmentCompat() {
getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener { getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
val names = enumValues<TvType>().sorted().map { it.name } val names = enumValues<TvType>().sorted().map { it.name }
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal } val default =
enumValues<TvType>().sorted().filter { it != TvType.NSFW }.map { it.ordinal }
val defaultSet = default.map { it.toString() }.toSet() val defaultSet = default.map { it.toString() }.toSet()
val currentList = try { val currentList = try {
settingsManager.getStringSet(getString(R.string.prefer_media_type_key), defaultSet)?.map { settingsManager.getStringSet(getString(R.string.prefer_media_type_key), defaultSet)
?.map {
it.toInt() it.toInt()
} }
} catch (e: Throwable) { null } ?: default } catch (e: Throwable) {
null
} ?: default
activity?.showMultiDialog( activity?.showMultiDialog(
names, names,
@ -89,20 +90,23 @@ class SettingsProviders : PreferenceFragmentCompat() {
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener { getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
activity?.getApiProviderLangSettings()?.let { current -> activity?.getApiProviderLangSettings()?.let { current ->
val langs = APIHolder.apis.map { it.lang }.toSet() val languages = APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
val currentList = ArrayList<Int>() val currentList = current.map {
for (i in current) { languages.indexOf(it)
currentList.add(langs.indexOf(i))
} }
val names = langs.map { val names = languages.map {
if (it == AllLanguagesName) {
Pair(it, getString(R.string.all_languages_preference))
} else {
val emoji = SubtitleHelper.getFlagFromIso(it) val emoji = SubtitleHelper.getFlagFromIso(it)
val name = SubtitleHelper.fromTwoLettersToLanguage(it) val name = SubtitleHelper.fromTwoLettersToLanguage(it)
val fullName = "$emoji $name" val fullName = "$emoji $name"
Pair(it, fullName) Pair(it, fullName)
} }
}
activity?.showMultiDialog( activity?.showMultiDialog(
names.map { it.second }, names.map { it.second },

View file

@ -33,8 +33,6 @@ import com.lagradost.cloudstream3.widget.LinearRecycleViewLayoutManager
import kotlinx.android.synthetic.main.add_repo_input.* import kotlinx.android.synthetic.main.add_repo_input.*
import kotlinx.android.synthetic.main.fragment_extensions.* import kotlinx.android.synthetic.main.fragment_extensions.*
const val PUBLIC_REPOSITORIES_LIST = "https://recloudstream.github.io/repos/"
class ExtensionsFragment : Fragment() { class ExtensionsFragment : Fragment() {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -186,15 +184,7 @@ class ExtensionsFragment : Fragment() {
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt( (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
0 0
)?.text?.toString()?.let { copy -> )?.text?.toString()?.let { copy ->
// Fix our own repo links and only paste the text if it's a link. dialog.repo_url_input?.setText(copy)
if (copy.startsWith("http")) {
val fixedUrl = if (copy.startsWith("https://cs.repo")) {
"https://" + copy.substringAfter("?")
} else {
copy
}
dialog.repo_url_input?.setText(fixedUrl)
}
} }
// dialog.list_repositories?.setOnClickListener { // dialog.list_repositories?.setOnClickListener {
@ -206,13 +196,12 @@ class ExtensionsFragment : Fragment() {
// dialog.text2?.text = provider.name // dialog.text2?.text = provider.name
dialog.apply_btt?.setOnClickListener secondListener@{ dialog.apply_btt?.setOnClickListener secondListener@{
val name = dialog.repo_name_input?.text?.toString() val name = dialog.repo_name_input?.text?.toString()
ioSafe {
val url = dialog.repo_url_input?.text?.toString() val url = dialog.repo_url_input?.text?.toString()
?.let { it1 -> RepositoryManager.parseRepoUrl(it1) }
if (url.isNullOrBlank()) { if (url.isNullOrBlank()) {
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT)
return@secondListener } else {
}
ioSafe {
val fixedName = if (!name.isNullOrBlank()) name val fixedName = if (!name.isNullOrBlank()) name
else RepositoryManager.parseRepository(url)?.name ?: "No name" else RepositoryManager.parseRepository(url)?.name ?: "No name"
@ -222,6 +211,7 @@ class ExtensionsFragment : Fragment() {
extensionViewModel.loadRepositories() extensionViewModel.loadRepositories()
this@ExtensionsFragment.activity?.downloadAllPluginsDialog(url, fixedName) this@ExtensionsFragment.activity?.downloadAllPluginsDialog(url, fixedName)
} }
}
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
} }
dialog.cancel_btt?.setOnClickListener { dialog.cancel_btt?.setOnClickListener {

View file

@ -7,6 +7,9 @@ import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager
@ -96,7 +99,14 @@ class SetupFragmentExtensions : Fragment() {
next_btt?.setOnClickListener { next_btt?.setOnClickListener {
// Continue setup // Continue setup
if (isSetup) if (isSetup)
if (
// If any available languages
apis.distinctBy { it.lang }.size > 1
) {
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
} else {
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media) findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media)
}
else else
findNavController().navigate(R.id.navigation_home) findNavController().navigate(R.id.navigation_home)
} }

View file

@ -15,7 +15,6 @@ import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.ui.settings.appLanguages
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
@ -85,7 +84,7 @@ class SetupFragmentLanguage : Fragment() {
&& PluginManager.getPluginsLocal().isEmpty() && PluginManager.getPluginsLocal().isEmpty()
//&& PREBUILT_REPOSITORIES.isNotEmpty() //&& PREBUILT_REPOSITORIES.isNotEmpty()
) R.id.action_navigation_global_to_navigation_setup_extensions ) R.id.action_navigation_global_to_navigation_setup_extensions
else R.id.action_navigation_setup_language_to_navigation_setup_media else R.id.action_navigation_setup_language_to_navigation_setup_provider_languages
findNavController().navigate( findNavController().navigate(
nextDestination, nextDestination,

View file

@ -12,6 +12,7 @@ import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -39,14 +40,21 @@ class SetupFragmentProviderLanguage : Fragment() {
val current = this.getApiProviderLangSettings() val current = this.getApiProviderLangSettings()
val langs = APIHolder.apis.map { it.lang }.toSet() val langs = APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
val currentList =
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
val currentList = current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
val languageNames = langs.map { val languageNames = langs.map {
if (it == AllLanguagesName) {
getString(R.string.all_languages_preference)
} else {
val emoji = SubtitleHelper.getFlagFromIso(it) val emoji = SubtitleHelper.getFlagFromIso(it)
val name = SubtitleHelper.fromTwoLettersToLanguage(it) val name = SubtitleHelper.fromTwoLettersToLanguage(it)
"$emoji $name" "$emoji $name"
} }
}
arrayAdapter.addAll(languageNames) arrayAdapter.addAll(languageNames)
listview1?.adapter = arrayAdapter listview1?.adapter = arrayAdapter

View file

@ -49,8 +49,7 @@ data class SaveCaptionStyle(
@JsonProperty("foregroundColor") var foregroundColor: Int, @JsonProperty("foregroundColor") var foregroundColor: Int,
@JsonProperty("backgroundColor") var backgroundColor: Int, @JsonProperty("backgroundColor") var backgroundColor: Int,
@JsonProperty("windowColor") var windowColor: Int, @JsonProperty("windowColor") var windowColor: Int,
@CaptionStyleCompat.EdgeType @JsonProperty("edgeType") var edgeType: @CaptionStyleCompat.EdgeType Int,
@JsonProperty("edgeType") var edgeType: Int,
@JsonProperty("edgeColor") var edgeColor: Int, @JsonProperty("edgeColor") var edgeColor: Int,
@FontRes @FontRes
@JsonProperty("typeface") var typeface: Int?, @JsonProperty("typeface") var typeface: Int?,

View file

@ -20,10 +20,13 @@ class CastOptionsProvider : OptionsProvider {
MediaIntentReceiver.ACTION_FORWARD, MediaIntentReceiver.ACTION_FORWARD,
MediaIntentReceiver.ACTION_STOP_CASTING MediaIntentReceiver.ACTION_STOP_CASTING
) )
val name = ControllerActivity::class.qualifiedName!!
val compatButtonAction = intArrayOf(1, 3) val compatButtonAction = intArrayOf(1, 3)
val notificationOptions = val notificationOptions =
NotificationOptions.Builder() NotificationOptions.Builder()
.setTargetActivityClassName(ControllerActivity::class.qualifiedName) .setTargetActivityClassName(name)
.setActions(buttonActions, compatButtonAction) .setActions(buttonActions, compatButtonAction)
.setForward30DrawableResId(R.drawable.go_forward_30) .setForward30DrawableResId(R.drawable.go_forward_30)
.setRewind30DrawableResId(R.drawable.go_back_30) .setRewind30DrawableResId(R.drawable.go_back_30)
@ -32,7 +35,7 @@ class CastOptionsProvider : OptionsProvider {
val mediaOptions = CastMediaOptions.Builder() val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions) .setNotificationOptions(notificationOptions)
.setExpandedControllerActivityClassName(ControllerActivity::class.qualifiedName) .setExpandedControllerActivityClassName(name)
.build() .build()
return CastOptions.Builder() return CastOptions.Builder()
@ -44,7 +47,7 @@ class CastOptionsProvider : OptionsProvider {
.build() .build()
} }
override fun getAdditionalSessionProviders(p0: Context?): MutableList<SessionProvider> { override fun getAdditionalSessionProviders(p0: Context): MutableList<SessionProvider> {
return Collections.emptyList() return Collections.emptyList()
} }
} }

View file

@ -329,6 +329,8 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
Vidmolyme(), Vidmolyme(),
Voe(), Voe(),
Moviehab(), Moviehab(),
MoviehabNet(),
Jeniusplay(),
Gdriveplayerapi(), Gdriveplayerapi(),
Gdriveplayerapp(), Gdriveplayerapp(),

View file

@ -159,17 +159,20 @@ object UIHelper {
url: String?, url: String?,
headers: Map<String, String>? = null, headers: Map<String, String>? = null,
@DrawableRes @DrawableRes
errorImageDrawable: Int? = null errorImageDrawable: Int? = null,
fadeIn: Boolean = true
): Boolean { ): Boolean {
if (this == null || url.isNullOrBlank()) return false if (this == null || url.isNullOrBlank()) return false
return try { return try {
val builder = GlideApp.with(this) val builder = GlideApp.with(this)
.load(GlideUrl(url) { headers ?: emptyMap() }).transition( .load(GlideUrl(url) { headers ?: emptyMap() })
DrawableTransitionOptions.withCrossFade()
)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.ALL) .diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
if (fadeIn)
req.transition(DrawableTransitionOptions.withCrossFade())
else req
}
val res = if (errorImageDrawable != null) val res = if (errorImageDrawable != null)
builder.error(errorImageDrawable).into(this) builder.error(errorImageDrawable).into(this)

View file

@ -287,39 +287,23 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"> tools:visibility="visible">
<androidx.viewpager.widget.ViewPager <androidx.viewpager2.widget.ViewPager2
android:id="@+id/home_preview_viewpager" android:id="@+id/home_preview_viewpager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:orientation="horizontal">
</androidx.viewpager.widget.ViewPager> </androidx.viewpager2.widget.ViewPager2>
<ImageView <ImageView
android:visibility="gone"
android:id="@+id/home_preview_image" android:id="@+id/home_preview_image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:alpha="0.8" android:alpha="0.8"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:visibility="gone"
tools:src="@drawable/example_poster" /> tools:src="@drawable/example_poster" />
<View
android:id="@+id/title_shadow_top"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="top"
android:alpha="1"
android:background="@drawable/background_shadow"
android:rotation="180"
android:visibility="visible" />
<View
android:id="@+id/title_shadow"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="bottom"
android:background="@drawable/background_shadow" />
<LinearLayout <LinearLayout
android:id="@+id/home_padding" android:id="@+id/home_padding"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -363,43 +347,14 @@
--> -->
<LinearLayout <LinearLayout
android:id="@+id/home_preview_title_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="100dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<TextView
android:id="@+id/home_preview_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:paddingBottom="10dp"
android:textColor="?attr/white"
android:textSize="17sp"
android:textStyle="bold"
tools:text="The Perfect Run" />
<!--<TextView
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:id="@+id/home_season_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="14sp"
tools:text="5 seasons 50 episodes" />-->
<TextView
android:id="@+id/home_preview_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:textColor="?attr/white"
android:textSize="14sp"
tools:text="Hello • World • Tags" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -5,7 +5,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal" android:orientation="horizontal"
android:keepScreenOn="true"
android:id="@+id/player_background" android:id="@+id/player_background"
app:backgroundTint="@android:color/black" app:backgroundTint="@android:color/black"
android:background="@android:color/black" android:background="@android:color/black"

View file

@ -6,7 +6,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/black" android:background="@android:color/black"
android:keepScreenOn="true"
android:orientation="horizontal" android:orientation="horizontal"
android:screenOrientation="sensorLandscape" android:screenOrientation="sensorLandscape"
app:backgroundTint="@android:color/black" app:backgroundTint="@android:color/black"

View file

@ -3,9 +3,9 @@
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:id="@+id/result_root" android:id="@+id/result_root"
style="@style/DarkFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
style="@style/DarkFragment"
android:background="?attr/primaryBlackBackground" android:background="?attr/primaryBlackBackground"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@ -122,9 +122,9 @@
android:id="@+id/result_finish_loading" android:id="@+id/result_finish_loading"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible">
android:orientation="vertical">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/result_scroll" android:id="@+id/result_scroll"
@ -139,18 +139,19 @@
android:background="?attr/primaryBlackBackground" android:background="?attr/primaryBlackBackground"
android:orientation="vertical"> android:orientation="vertical">
<!--
<com.facebook.shimmer.ShimmerFrameLayout <com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
android:visibility="gone"
android:id="@+id/result_trailer_loading" android:id="@+id/result_trailer_loading"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"
app:shimmer_auto_start="true" app:shimmer_auto_start="true"
app:shimmer_base_alpha="0.2" app:shimmer_base_alpha="0.2"
app:shimmer_duration="@integer/loading_time" app:shimmer_duration="@integer/loading_time"
app:shimmer_highlight_alpha="0.3"> app:shimmer_highlight_alpha="0.3"
tools:visibility="gone">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -159,22 +160,15 @@
android:orientation="vertical"> android:orientation="vertical">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:background="@color/grayShimmer"
app:cardCornerRadius="@dimen/loading_radius"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="150dp" android:layout_height="150dp"
android:foreground="@drawable/outline_drawable" /> android:background="@color/grayShimmer"
android:foreground="@drawable/outline_drawable"
app:cardCornerRadius="@dimen/loading_radius" />
</LinearLayout> </LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout> </com.facebook.shimmer.ShimmerFrameLayout>
-->
<FrameLayout
android:descendantFocusability="blocksDescendants"
android:id="@+id/result_smallscreen_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<include layout="@layout/fragment_trailer" />
</FrameLayout>
<!-- <!--
<FrameLayout <FrameLayout
@ -272,6 +266,36 @@
app:tint="?attr/textColor" /> app:tint="?attr/textColor" />
</LinearLayout> </LinearLayout>
</FrameLayout>--> </FrameLayout>-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<FrameLayout
android:id="@+id/result_smallscreen_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<include layout="@layout/fragment_trailer" />
</FrameLayout>
<FrameLayout
android:id="@+id/result_poster_background_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/result_poster_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
<View
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@drawable/background_shadow" />
</FrameLayout>
</FrameLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -281,6 +305,7 @@
android:paddingStart="@dimen/result_padding" android:paddingStart="@dimen/result_padding"
android:paddingEnd="@dimen/result_padding"> android:paddingEnd="@dimen/result_padding">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -289,10 +314,12 @@
android:orientation="horizontal" android:orientation="horizontal"
android:visibility="visible"> android:visibility="visible">
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/result_poster_holder" android:id="@+id/result_poster_holder"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone"
app:cardCornerRadius="@dimen/rounded_image_radius"> app:cardCornerRadius="@dimen/rounded_image_radius">
<ImageView <ImageView
@ -312,7 +339,6 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:layout_marginHorizontal="10dp"
android:id="@+id/result_title" android:id="@+id/result_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -324,7 +350,6 @@
tools:text="The Perfect Run The Perfect Run" /> tools:text="The Perfect Run The Perfect Run" />
<com.lagradost.cloudstream3.widget.FlowLayout <com.lagradost.cloudstream3.widget.FlowLayout
android:layout_marginHorizontal="10dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:itemSpacing="10dp"> app:itemSpacing="10dp">
@ -366,20 +391,19 @@
The focus outline now settles between the poster and text. The focus outline now settles between the poster and text.
--> -->
<FrameLayout <FrameLayout
android:layout_marginHorizontal="5dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <TextView
android:padding="5dp"
android:maxLength="1000"
android:ellipsize="end"
android:id="@+id/result_description" android:id="@+id/result_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end"
android:foreground="@drawable/outline_drawable" android:foreground="@drawable/outline_drawable"
android:maxLength="1000"
android:nextFocusUp="@id/result_back" android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_bookmark_button" android:nextFocusDown="@id/result_bookmark_button"
android:paddingTop="5dp"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="15sp" android:textSize="15sp"
tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. " /> tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. " />
@ -389,6 +413,7 @@
android:layout_height="30dp" android:layout_height="30dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:src="@drawable/background_shadow" android:src="@drawable/background_shadow"
android:visibility="gone"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
@ -431,23 +456,23 @@
tools:text="Cast: Joe Ligma" /> tools:text="Cast: Joe Ligma" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
tools:visibility="gone"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusDown="@id/result_play_movie"
android:id="@+id/result_cast_items" android:id="@+id/result_cast_items"
android:layout_width="match_parent" android:layout_width="match_parent"
android:descendantFocusability="afterDescendants"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:fadingEdge="horizontal" android:fadingEdge="horizontal"
android:focusableInTouchMode="false"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusDown="@id/result_play_movie"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingTop="5dp" android:paddingTop="5dp"
android:requiresFadingEdge="horizontal" android:requiresFadingEdge="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2" tools:itemCount="2"
tools:listitem="@layout/cast_item" /> tools:listitem="@layout/cast_item"
tools:visibility="gone" />
<TextView <TextView
android:id="@+id/result_vpn" android:id="@+id/result_vpn"
@ -847,29 +872,29 @@
<!--TODO add next airing--> <!--TODO add next airing-->
<LinearLayout <LinearLayout
android:id="@+id/result_next_airing_holder" android:id="@+id/result_next_airing_holder"
android:layout_gravity="start"
android:orientation="horizontal"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="horizontal">
<TextView <TextView
android:gravity="center"
android:id="@+id/result_next_airing" android:id="@+id/result_next_airing"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
android:textSize="17sp" android:textSize="17sp"
android:textStyle="normal" android:textStyle="normal"
tools:text="Episode 1022 will be released in" /> tools:text="Episode 1022 will be released in" />
<TextView <TextView
android:paddingEnd="5dp"
android:paddingStart="5dp"
android:gravity="center"
android:id="@+id/result_next_airing_time" android:id="@+id/result_next_airing_time"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="17sp" android:textSize="17sp"
android:textStyle="normal" android:textStyle="normal"
@ -917,9 +942,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:clipToPadding="false" android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:descendantFocusability="afterDescendants" android:descendantFocusability="afterDescendants"
android:paddingBottom="100dp" android:paddingBottom="100dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_episode_both_tv" /> tools:listitem="@layout/result_episode_both_tv" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -6,7 +6,6 @@
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="visible" android:visibility="visible"
android:orientation="horizontal" android:orientation="horizontal"
android:keepScreenOn="true"
android:id="@+id/player_background" android:id="@+id/player_background"
app:backgroundTint="@android:color/black" app:backgroundTint="@android:color/black"
android:background="@android:color/black" android:background="@android:color/black"

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="?attr/primaryGrayBackground"
android:layout_height="match_parent">
<ImageView
android:id="@+id/home_scroll_preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@drawable/example_poster" />
<View
android:id="@+id/title_shadow_top"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_gravity="top"
android:alpha="1"
android:background="@drawable/background_shadow"
android:rotation="180"
android:visibility="visible" />
<View
android:id="@+id/title_shadow"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_gravity="bottom"
android:background="@drawable/background_shadow" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="100dp"
android:orientation="vertical">
<TextView
android:id="@+id/home_scroll_preview_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:paddingHorizontal="30dp"
android:paddingBottom="10dp"
android:textColor="?attr/white"
android:textSize="17sp"
android:textStyle="bold"
tools:text="The Perfect Run" />
<!--<TextView
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:id="@+id/home_season_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/white"
android:textSize="14sp"
tools:text="5 seasons 50 episodes" />-->
<TextView
android:id="@+id/home_scroll_preview_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:paddingStart="30dp"
android:paddingEnd="30dp"
android:textColor="?attr/white"
android:textSize="14sp"
tools:text="Hello • World • Tags" />
</LinearLayout>
</FrameLayout>

View file

@ -38,26 +38,41 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<View <!-- <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/player_gradient_tv" /> android:background="@drawable/player_gradient_tv" />
-->
<!--
<ImageView
android:id="@+id/result_trailer_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:scaleType="centerCrop" />-->
<View
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:background="@drawable/background_shadow" />
<TextView <TextView
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@android:color/white"
android:text="@string/trailer"
android:padding="10dp"
android:layout_gravity="start|bottom"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_gravity="start|bottom"
android:padding="10dp"
android:text="@string/trailer"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView <ImageView
android:layout_gravity="center"
android:src="@drawable/play_button"
android:layout_width="60dp" android:layout_width="60dp"
android:layout_height="60dp" /> android:layout_height="60dp"
android:layout_gravity="center"
android:src="@drawable/play_button" />
</FrameLayout> </FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@ -67,54 +82,54 @@
<!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator--> <!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator-->
<ProgressBar <ProgressBar
app:layout_constraintBottom_toBottomOf="parent" android:id="@+id/player_buffering"
app:layout_constraintLeft_toLeftOf="parent" android:layout_width="wrap_content"
app:layout_constraintRight_toRightOf="parent" android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" android:layout_gravity="center"
android:focusable="false"
android:clickable="false" android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false" android:focusableInTouchMode="false"
android:indeterminate="true" android:indeterminate="true"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"
android:id="@+id/player_buffering"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
android:id="@+id/player_pause_play_holder_holder"
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="100dp" android:layout_height="100dp"
android:id="@+id/player_pause_play_holder_holder"> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout <FrameLayout
tools:ignore="uselessParent"
android:id="@+id/player_pause_play_holder" android:id="@+id/player_pause_play_holder"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
tools:ignore="uselessParent">
<ImageView <ImageView
app:tint="@color/white"
android:id="@+id/player_pause_play" android:id="@+id/player_pause_play"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button"
android:nextFocusLeft="@id/exo_rew" android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd" android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back" android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock" android:nextFocusDown="@id/player_lock"
android:layout_gravity="center"
android:src="@drawable/netflix_pause" android:src="@drawable/netflix_pause"
android:background="@drawable/video_tap_button" app:tint="@color/white"
android:layout_width="70dp"
android:layout_height="70dp"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
</FrameLayout> </FrameLayout>
@ -135,12 +150,12 @@
android:id="@+id/player_rew_holder" android:id="@+id/player_rew_holder"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.5"
android:layout_gravity="center_vertical|start" android:layout_gravity="center_vertical|start"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/player_ffwd_holder" app:layout_constraintRight_toLeftOf="@id/player_ffwd_holder"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.5">
<TextView <TextView
android:id="@+id/exo_rew_text" android:id="@+id/exo_rew_text"
@ -169,21 +184,21 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:scaleX="-1" android:scaleX="-1"
android:src="@drawable/netflix_skip_forward" android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
android:id="@+id/player_ffwd_holder" android:id="@+id/player_ffwd_holder"
android:layout_width="0dp" android:layout_width="0dp"
app:layout_constraintWidth_percent="0.5"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end" android:layout_gravity="center_vertical|end"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/player_rew_holder" app:layout_constraintLeft_toRightOf="@id/player_rew_holder"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.5">
<TextView <TextView
android:id="@+id/exo_ffwd_text" android:id="@+id/exo_ffwd_text"
@ -209,8 +224,8 @@
android:padding="10dp" android:padding="10dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/netflix_skip_forward" android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="@color/white"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
</FrameLayout> </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -230,60 +245,60 @@
<ImageButton <ImageButton
android:id="@id/exo_prev" android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous" style="@style/ExoMediaButton.Previous"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageButton <ImageButton
android:id="@id/exo_repeat_toggle" android:id="@id/exo_repeat_toggle"
style="@style/ExoMediaButton" style="@style/ExoMediaButton"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageButton <ImageButton
android:id="@id/exo_next" android:id="@id/exo_next"
style="@style/ExoMediaButton.Next" style="@style/ExoMediaButton.Next"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageButton <ImageButton
android:id="@id/exo_vr" android:id="@id/exo_vr"
style="@style/ExoMediaButton.VR" style="@style/ExoMediaButton.VR"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in" android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ImageButton <ImageButton
android:id="@id/exo_play" android:id="@id/exo_play"
app:tint="?attr/colorPrimaryDark" android:layout_width="0dp"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_width="0dp" /> android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" />
<ImageButton <ImageButton
android:id="@id/exo_pause" android:id="@id/exo_pause"
app:tint="?attr/colorPrimaryDark" android:layout_width="0dp"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_width="0dp" /> android:tintMode="src_in"
app:tint="?attr/colorPrimaryDark"
tools:ignore="ContentDescription" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:background="?android:attr/selectableItemBackgroundBorderless"
android:id="@+id/player_open_source" android:id="@+id/player_open_source"
app:tint="@color/white"
android:src="@drawable/ic_baseline_public_24"
android:layout_margin="20dp"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="24dp"
android:layout_margin="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_public_24"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:tint="@color/white" />
<LinearLayout <LinearLayout
android:id="@+id/bottom_player_bar" android:id="@+id/bottom_player_bar"
@ -296,10 +311,10 @@
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <LinearLayout
android:layout_weight="1"
android:id="@+id/player_video_bar" android:id="@+id/player_video_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@ -350,14 +365,14 @@
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:background="?android:attr/selectableItemBackgroundBorderless"
android:id="@+id/player_fullscreen" android:id="@+id/player_fullscreen"
app:tint="@color/white"
android:layout_gravity="center_vertical"
android:src="@drawable/baseline_fullscreen_24"
android:layout_marginEnd="20dp"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="30dp" /> android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -397,14 +412,14 @@
style="@android:style/Widget.Material.ProgressBar.Horizontal" style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="4dp" android:layout_width="4dp"
android:layout_height="150dp" android:layout_height="150dp"
android:layout_centerInParent="true"
android:layout_gravity="end|center_vertical" android:layout_gravity="end|center_vertical"
android:layout_marginStart="40dp" android:layout_marginStart="40dp"
android:indeterminate="false" android:indeterminate="false"
android:max="100" android:max="100"
android:progress="100" android:progress="100"
android:progressDrawable="@drawable/progress_drawable_vertical" android:progressDrawable="@drawable/progress_drawable_vertical"
tools:progress="30" tools:progress="30" />
android:layout_centerInParent="true" />
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout

View file

@ -522,13 +522,6 @@
app:exitAnim="@anim/exit_anim" app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim" app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" /> app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_setup_language_to_navigation_setup_media"
app:destination="@id/navigation_setup_media"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment> </fragment>
<action <action

View file

@ -643,4 +643,5 @@
<string name="player_settings_play_in_web">Web Video Cast</string> <string name="player_settings_play_in_web">Web Video Cast</string>
<string name="player_settings_play_in_browser">Browser</string> <string name="player_settings_play_in_browser">Browser</string>
<string name="app_not_found_error">App not found</string> <string name="app_not_found_error">App not found</string>
<string name="all_languages_preference">All Languages</string>
</resources> </resources>

View file

@ -81,6 +81,9 @@
<item name="chipStrokeColor">@color/transparent</item> <item name="chipStrokeColor">@color/transparent</item>
<item name="textColor">@color/chip_color_text</item> <item name="textColor">@color/chip_color_text</item>
<item name="android:textColor">@color/chip_color_text</item> <item name="android:textColor">@color/chip_color_text</item>
<item name="checkedIconTint">@color/chip_color_text</item>
<item name="fontFamily">@font/google_sans</item>
<item name="android:fontFamily">@font/google_sans</item>
</style> </style>
<style name="AmoledMode"> <style name="AmoledMode">
@ -137,13 +140,13 @@
</style> </style>
<style name="OverlayPrimaryColorMonetTwo"> <style name="OverlayPrimaryColorMonetTwo">
<item name="colorPrimary">@color/material_dynamic_tertiary80</item> <item name="colorPrimary">@color/material_dynamic_secondary80</item>
<item name="android:colorPrimary">@color/material_dynamic_tertiary80</item> <item name="android:colorPrimary">@color/material_dynamic_secondary80</item>
<item name="colorPrimaryDark">@color/material_dynamic_tertiary30</item> <item name="colorPrimaryDark">@color/material_dynamic_secondary30</item>
<item name="colorAccent">@color/material_dynamic_tertiary80</item> <item name="colorAccent">@color/material_dynamic_secondary80</item>
<item name="colorOnPrimary">@color/material_dynamic_tertiary20</item> <item name="colorOnPrimary">@color/material_dynamic_secondary20</item>
<!-- Needed for leanback fuckery --> <!-- Needed for leanback fuckery -->
<item name="android:colorAccent">@color/material_dynamic_tertiary30</item> <item name="android:colorAccent">@color/material_dynamic_secondary30</item>
</style> </style>
<style name="OverlayPrimaryColorBlue"> <style name="OverlayPrimaryColorBlue">

View file

@ -2,7 +2,6 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen 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">
<Preference <Preference
app:isPreferenceVisible="false"
android:icon="@drawable/ic_baseline_language_24" android:icon="@drawable/ic_baseline_language_24"
android:key="@string/provider_lang_key" android:key="@string/provider_lang_key"
android:title="@string/provider_lang_settings" /> android:title="@string/provider_lang_settings" />