From 0f8a2df9e5046c5f1822f19e85dd2248e6b1737b Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Tue, 23 Aug 2022 21:28:42 +0200 Subject: [PATCH 1/5] Optimized plugin loading --- .../lagradost/cloudstream3/MainActivity.kt | 73 ++++++++++++------- .../cloudstream3/plugins/PluginManager.kt | 52 ++++++++----- .../cloudstream3/ui/home/HomeFragment.kt | 9 +-- .../cloudstream3/ui/home/HomeViewModel.kt | 43 ++++++----- 4 files changed, 108 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 49864e65..629406aa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -84,6 +84,8 @@ import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ResponseParser import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result_swipe.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.io.File import java.net.URI import kotlin.reflect.KClass @@ -131,6 +133,10 @@ var app = Requests(responseParser = object : ResponseParser { class MainActivity : AppCompatActivity(), ColorPickerDialogListener { companion object { const val TAG = "MAINACT" + + /** + * Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread + * */ val afterPluginsLoadedEvent = Event() val mainPluginsLoadedEvent = Event() // homepage api, used to speed up time to load for homepage @@ -236,6 +242,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { override fun onResume() { super.onResume() + afterPluginsLoadedEvent += ::onAllPluginsLoaded try { if (isCastApiAvailable()) { //mCastSession = mSessionManager.currentCastSession @@ -325,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { broadcastIntent.action = "restart_service" broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java) this.sendBroadcast(broadcastIntent) + afterPluginsLoadedEvent -= ::onAllPluginsLoaded super.onDestroy() } @@ -414,6 +422,36 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } + private val pluginsLock = Mutex() + private fun onAllPluginsLoaded(success: Boolean = false) { + ioSafe { + pluginsLock.withLock { + // Load cloned sites after plugins have been loaded since clones depend on plugins. + try { + getKey>(USER_PROVIDER_API)?.let { list -> + list.forEach { custom -> + allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } + ?.let { + allProviders.add(it.javaClass.newInstance().apply { + name = custom.name + lang = custom.lang + mainUrl = custom.url.trimEnd('/') + canBeOverridden = false + }) + } + } + } + // it.hashCode() is not enough to make sure they are distinct + apis = allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name } + APIHolder.apiMap = null + } catch (e: Exception) { + logError(e) + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -446,36 +484,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { mainPluginsLoadedEvent.invoke(false) } - if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { - PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) - } else { - PluginManager.loadAllOnlinePlugins(this@MainActivity) - } - - PluginManager.loadAllLocalPlugins(this@MainActivity) - - // Load cloned sites after plugins have been loaded since clones depend on plugins. - try { - getKey>(USER_PROVIDER_API)?.let { list -> - list.forEach { custom -> - allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } - ?.let { - allProviders.add(it.javaClass.newInstance().apply { - name = custom.name - lang = custom.lang - mainUrl = custom.url.trimEnd('/') - canBeOverridden = false - }) - } - } + ioSafe { + if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { + PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) + } else { + PluginManager.loadAllOnlinePlugins(this@MainActivity) } - apis = allProviders.distinctBy { it } - APIHolder.apiMap = null - } catch (e: Exception) { - logError(e) } - afterPluginsLoadedEvent.invoke(true) + ioSafe { + PluginManager.loadAllLocalPlugins(this@MainActivity) + } } // ioSafe { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 96a2222e..ef7dbf35 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -21,12 +21,15 @@ 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.mvvm.logError import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.extractorApis import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.acra.log.debug import java.io.File import java.io.InputStreamReader import java.util.* @@ -162,7 +165,7 @@ object PluginManager { val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN } - var allCurrentOutDatedPlugins: Set = emptySet() + // var allCurrentOutDatedPlugins: Set = emptySet() suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { return (getPluginsOnline().firstOrNull { it.internalName == apiName } @@ -184,6 +187,19 @@ object PluginManager { * 4. Else load the plugin normally **/ fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { + // Load all plugins as fast as possible! + (getPluginsOnline()).toList().apmap { pluginData -> + loadPlugin( + activity, + File(pluginData.filePath), + pluginData + ) + } + + ioSafe { + afterPluginsLoadedEvent.invoke(true) + } + val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES @@ -198,29 +214,28 @@ object PluginManager { OnlinePluginData(savedData, onlineData) } }.flatten().distinctBy { it.onlineData.second.url } - allCurrentOutDatedPlugins = outdatedPlugins.toSet() - Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}") + debug { + "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}" + } - outdatedPlugins.apmap { - if (it.isDisabled) { - return@apmap - } else if (it.isOutdated) { + outdatedPlugins.apmap { pluginData -> + if (pluginData.isDisabled) { + unloadPlugin(pluginData.savedData.filePath) + } else if (pluginData.isOutdated) { downloadAndLoadPlugin( activity, - it.onlineData.second.url, - it.savedData.internalName, - it.onlineData.first - ) - } else { - loadPlugin( - activity, - File(it.savedData.filePath), - it.savedData + pluginData.onlineData.second.url, + pluginData.savedData.internalName, + pluginData.onlineData.first ) } } + ioSafe { + afterPluginsLoadedEvent.invoke(true) + } + Log.i(TAG, "Plugin update done!") } @@ -256,6 +271,7 @@ object PluginManager { } loadedLocalPlugins = true + afterPluginsLoadedEvent.invoke(true) } /** @@ -380,7 +396,9 @@ object PluginManager { try { val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique val fileName = getPluginSanitizedFileName(internalName) - Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") + unloadPlugin("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3") + + Log.d(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName) return loadPlugin( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 975545ee..539ce843 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -456,20 +456,17 @@ class HomeFragment : Fragment() { homeViewModel.loadStoredData(list) } - private var hasBeenConsumed = false private fun firstLoadHomePage(successful: Boolean = false) { // dirty hack to make it only load once - if(hasBeenConsumed) return - hasBeenConsumed = true - loadHomePage(successful) + loadHomePage(false) } - private fun loadHomePage(successful: Boolean = false) { + private fun loadHomePage(forceReload: Boolean = true) { val apiName = context?.getKey(USER_SELECTED_HOMEPAGE_API) if (homeViewModel.apiName.value != apiName || apiName == null) { //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) - homeViewModel.loadAndCancel(apiName) + homeViewModel.loadAndCancel(apiName, forceReload) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 7319c5b2..daff5e4a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -264,25 +264,30 @@ class HomeViewModel : ViewModel() { } } - fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe { - val api = getApiFromNameNull(preferredApiName) - if (preferredApiName == noneApi.name){ - setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) - loadAndCancel(noneApi) - } - else if (preferredApiName == randomApi.name || api == null) { - val validAPIs = context?.filterProviderByPreferredMedia() - if (validAPIs.isNullOrEmpty()) { - // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded - loadAndCancel(noneApi) - } else { - val apiRandom = validAPIs.random() - loadAndCancel(apiRandom) - setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) + fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) = + viewModelScope.launchSafe { + // Since plugins are loaded in stages this function can get called multiple times. + // This makes the home page reload only if it's a failure or loading + if (!forceReload && page.value is Resource.Success) { + return@launchSafe + } + val api = getApiFromNameNull(preferredApiName) + if (preferredApiName == noneApi.name) { + setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) + loadAndCancel(noneApi) + } else if (preferredApiName == randomApi.name || api == null) { + val validAPIs = context?.filterProviderByPreferredMedia() + if (validAPIs.isNullOrEmpty()) { + // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded + loadAndCancel(noneApi) + } else { + val apiRandom = validAPIs.random() + loadAndCancel(apiRandom) + setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) + } + } else { + setKey(USER_SELECTED_HOMEPAGE_API, api.name) + loadAndCancel(api) } - } else { - setKey(USER_SELECTED_HOMEPAGE_API, api.name) - loadAndCancel(api) } - } } \ No newline at end of file From 92dbbf86b2210ab92957512fcfb5240f47248df9 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Wed, 24 Aug 2022 00:12:56 +0200 Subject: [PATCH 2/5] =?UTF-8?q?Switched=20prefer=5Fmedia=5Ftype=5Fkey=20to?= =?UTF-8?q?=20fix=20crashes=20(you=20arent=20supposed=20to=20use=20this=20?= =?UTF-8?q?yet=20anyways=20=F0=9F=92=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f0fd0102..4430d224 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,7 +42,7 @@ primary_color_key restore_key backup_key - prefer_media_type_key + prefer_media_type_key_2 app_theme_key episode_sync_enabled_key log_enabled_key From 82d416ad7f47fb5685f86b845c9df183f7683116 Mon Sep 17 00:00:00 2001 From: antonydp <38143733+antonydp@users.noreply.github.com> Date: Wed, 24 Aug 2022 00:14:07 +0200 Subject: [PATCH 3/5] Cloudflare getCookieHeaders (#56) --- .../cloudstream3/network/CloudflareKiller.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt index ceacb9aa..7dc8dba7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt @@ -9,6 +9,7 @@ import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.nicehttp.cookies import kotlinx.coroutines.runBlocking import okhttp3.* +import java.net.URI @AnyThread @@ -30,6 +31,17 @@ class CloudflareKiller : Interceptor { val savedCookies: MutableMap> = mutableMapOf() + /** + * Gets the headers with cookies, webview user agent included! + * */ + fun getCookieHeaders(url: String): Headers { + val userAgentHeaders = WebViewResolver.webViewUserAgent?.let { + mapOf("user-agent" to it) + } ?: emptyMap() + + return getHeaders(userAgentHeaders, savedCookies[URI(url).host] ?: emptyMap()) + } + override fun intercept(chain: Interceptor.Chain): Response = runBlocking { val request = chain.request() val cookies = savedCookies[request.url.host] From bbdb1d7ad31fb570fa00dbf3017b25792958f7a6 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Wed, 24 Aug 2022 03:10:06 +0200 Subject: [PATCH 4/5] Add toggle to disable autoplay Fixes #57 --- .../cloudstream3/ui/player/CS3IPlayer.kt | 21 +++++++++++++++++-- app/src/main/res/values/strings.xml | 5 +++++ app/src/main/res/xml/settings_player.xml | 6 ++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 0eb72f19..5f6ef921 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -6,6 +6,7 @@ import android.os.Handler import android.os.Looper import android.util.Log import android.widget.FrameLayout +import androidx.preference.PreferenceManager import com.google.android.exoplayer2.* import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource @@ -742,7 +743,15 @@ class CS3IPlayer : IPlayer { } Player.STATE_ENDED -> { - handleEvent(CSPlayerEvent.NextEpisode) + // Only play next episode if autoplay is on (default) + if (PreferenceManager.getDefaultSharedPreferences(context) + ?.getBoolean( + context.getString(com.lagradost.cloudstream3.R.string.autoplay_next_key), + true + ) == true + ) { + handleEvent(CSPlayerEvent.NextEpisode) + } } Player.STATE_BUFFERING -> { updatedTime() @@ -780,7 +789,15 @@ class CS3IPlayer : IPlayer { } Player.STATE_ENDED -> { - handleEvent(CSPlayerEvent.NextEpisode) + // Only play next episode if autoplay is on (default) + if (PreferenceManager.getDefaultSharedPreferences(context) + ?.getBoolean( + context.getString(com.lagradost.cloudstream3.R.string.autoplay_next_key), + true + ) == true + ) { + handleEvent(CSPlayerEvent.NextEpisode) + } } Player.STATE_BUFFERING -> { updatedTime() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4430d224..1b47b180 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ double_tap_pause_enabled_key double_tap_seek_time_key swipe_vertical_enabled_key + autoplay_next_key display_sub_key show_fillers_key show_trailers_key @@ -223,6 +224,10 @@ Swipe left or right to control time in the videoplayer Swipe to change settings Swipe on the left or right side to change brightness or volume + + Autoplay next episode + Start the next episode when the current one ends + Double tap to seek Double tap to pause Player seek amount diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index d98f506f..a7e36488 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -58,6 +58,12 @@ android:title="@string/swipe_to_change_settings" android:summary="@string/swipe_to_change_settings_des" app:defaultValue="true" /> + Date: Wed, 24 Aug 2022 04:21:46 +0200 Subject: [PATCH 5/5] Fix randomized homepage --- .../lagradost/cloudstream3/plugins/PluginManager.kt | 9 +++++++-- .../lagradost/cloudstream3/ui/home/HomeViewModel.kt | 13 ++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index ef7dbf35..59dae640 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -168,8 +168,13 @@ object PluginManager { // var allCurrentOutDatedPlugins: Set = emptySet() suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { - return (getPluginsOnline().firstOrNull { it.internalName == apiName } - ?: getPluginsLocal().firstOrNull { it.internalName == apiName })?.let { savedData -> + return (getPluginsOnline().firstOrNull { + // Most of the time the provider ends with Provider which isn't part of the api name + it.internalName.replace("provider", "", ignoreCase = true) == apiName + } + ?: getPluginsLocal().firstOrNull { + it.internalName.replace("provider", "", ignoreCase = true) == apiName + })?.let { savedData -> // OnlinePluginData(savedData, onlineData) loadPlugin( activity, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index daff5e4a..1d6ed584 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -267,15 +267,18 @@ class HomeViewModel : ViewModel() { fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) = viewModelScope.launchSafe { // Since plugins are loaded in stages this function can get called multiple times. - // This makes the home page reload only if it's a failure or loading - if (!forceReload && page.value is Resource.Success) { + // The issue with this is that the homepage may be fetched multiple times while the first request is loading + val api = getApiFromNameNull(preferredApiName) + if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) { return@launchSafe } - val api = getApiFromNameNull(preferredApiName) - if (preferredApiName == noneApi.name) { + // If the plugin isn't loaded yet. (Does not set the key) + if (api == null) { + loadAndCancel(noneApi) + } else if (preferredApiName == noneApi.name) { setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) loadAndCancel(noneApi) - } else if (preferredApiName == randomApi.name || api == null) { + } else if (preferredApiName == randomApi.name) { val validAPIs = context?.filterProviderByPreferredMedia() if (validAPIs.isNullOrEmpty()) { // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded