Merge remote-tracking branch 'origin/master'

This commit is contained in:
Antony 2022-08-24 22:49:30 +02:00
commit aa3b94d186
9 changed files with 162 additions and 76 deletions

View file

@ -84,6 +84,8 @@ import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser import com.lagradost.nicehttp.ResponseParser
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -131,6 +133,10 @@ var app = Requests(responseParser = object : ResponseParser {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener { class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
companion object { companion object {
const val TAG = "MAINACT" 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<Boolean>() val afterPluginsLoadedEvent = Event<Boolean>()
val mainPluginsLoadedEvent = val mainPluginsLoadedEvent =
Event<Boolean>() // homepage api, used to speed up time to load for homepage Event<Boolean>() // homepage api, used to speed up time to load for homepage
@ -236,6 +242,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
afterPluginsLoadedEvent += ::onAllPluginsLoaded
try { try {
if (isCastApiAvailable()) { if (isCastApiAvailable()) {
//mCastSession = mSessionManager.currentCastSession //mCastSession = mSessionManager.currentCastSession
@ -325,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
broadcastIntent.action = "restart_service" broadcastIntent.action = "restart_service"
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java) broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
this.sendBroadcast(broadcastIntent) this.sendBroadcast(broadcastIntent)
afterPluginsLoadedEvent -= ::onAllPluginsLoaded
super.onDestroy() 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<Array<SettingsGeneral.CustomSite>>(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?) { override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this) app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -446,36 +484,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
mainPluginsLoadedEvent.invoke(false) mainPluginsLoadedEvent.invoke(false)
} }
if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { ioSafe {
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) {
} else { PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
PluginManager.loadAllOnlinePlugins(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<Array<SettingsGeneral.CustomSite>>(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
})
}
}
} }
apis = allProviders.distinctBy { it }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
} }
afterPluginsLoadedEvent.invoke(true) ioSafe {
PluginManager.loadAllLocalPlugins(this@MainActivity)
}
} }
// ioSafe { // ioSafe {

View file

@ -9,6 +9,7 @@ import com.lagradost.nicehttp.Requests.Companion.await
import com.lagradost.nicehttp.cookies import com.lagradost.nicehttp.cookies
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.* import okhttp3.*
import java.net.URI
@AnyThread @AnyThread
@ -30,6 +31,17 @@ class CloudflareKiller : Interceptor {
val savedCookies: MutableMap<String, Map<String, String>> = mutableMapOf() val savedCookies: MutableMap<String, Map<String, String>> = 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 { override fun intercept(chain: Interceptor.Chain): Response = runBlocking {
val request = chain.request() val request = chain.request()
val cookies = savedCookies[request.url.host] val cookies = savedCookies[request.url.host]

View file

@ -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.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.APIHolder.removePluginMapping import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
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.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.acra.log.debug
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.* import java.util.*
@ -162,11 +165,16 @@ object PluginManager {
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
} }
var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet() // var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet()
suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
return (getPluginsOnline().firstOrNull { it.internalName == apiName } return (getPluginsOnline().firstOrNull {
?: getPluginsLocal().firstOrNull { it.internalName == apiName })?.let { savedData -> // 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) // OnlinePluginData(savedData, onlineData)
loadPlugin( loadPlugin(
activity, activity,
@ -184,6 +192,19 @@ object PluginManager {
* 4. Else load the plugin normally * 4. Else load the plugin normally
**/ **/
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { 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<Array<RepositoryData>>(REPOSITORIES_KEY) val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES ?: emptyArray()) + PREBUILT_REPOSITORIES
@ -198,29 +219,28 @@ object PluginManager {
OnlinePluginData(savedData, onlineData) OnlinePluginData(savedData, onlineData)
} }
}.flatten().distinctBy { it.onlineData.second.url } }.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 { outdatedPlugins.apmap { pluginData ->
if (it.isDisabled) { if (pluginData.isDisabled) {
return@apmap unloadPlugin(pluginData.savedData.filePath)
} else if (it.isOutdated) { } else if (pluginData.isOutdated) {
downloadAndLoadPlugin( downloadAndLoadPlugin(
activity, activity,
it.onlineData.second.url, pluginData.onlineData.second.url,
it.savedData.internalName, pluginData.savedData.internalName,
it.onlineData.first pluginData.onlineData.first
)
} else {
loadPlugin(
activity,
File(it.savedData.filePath),
it.savedData
) )
} }
} }
ioSafe {
afterPluginsLoadedEvent.invoke(true)
}
Log.i(TAG, "Plugin update done!") Log.i(TAG, "Plugin update done!")
} }
@ -256,6 +276,7 @@ object PluginManager {
} }
loadedLocalPlugins = true loadedLocalPlugins = true
afterPluginsLoadedEvent.invoke(true)
} }
/** /**
@ -380,7 +401,9 @@ object PluginManager {
try { try {
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
val fileName = getPluginSanitizedFileName(internalName) 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 // 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) val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
return loadPlugin( return loadPlugin(

View file

@ -148,7 +148,6 @@ class GithubApi(index: Int) : InAppAuthAPIManager(index){
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
switchToOldAccount()
} }
switchToOldAccount() switchToOldAccount()
return false return false
@ -179,4 +178,4 @@ class GithubApi(index: Int) : InAppAuthAPIManager(index){
) )
} }
} }
} }

View file

@ -456,20 +456,17 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list) homeViewModel.loadStoredData(list)
} }
private var hasBeenConsumed = false
private fun firstLoadHomePage(successful: Boolean = false) { private fun firstLoadHomePage(successful: Boolean = false) {
// dirty hack to make it only load once // dirty hack to make it only load once
if(hasBeenConsumed) return loadHomePage(false)
hasBeenConsumed = true
loadHomePage(successful)
} }
private fun loadHomePage(successful: Boolean = false) { private fun loadHomePage(forceReload: Boolean = true) {
val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API) val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API)
if (homeViewModel.apiName.value != apiName || apiName == null) { if (homeViewModel.apiName.value != apiName || apiName == null) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName) homeViewModel.loadAndCancel(apiName, forceReload)
} }
} }

View file

@ -264,25 +264,33 @@ class HomeViewModel : ViewModel() {
} }
} }
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe { fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
val api = getApiFromNameNull(preferredApiName) viewModelScope.launchSafe {
if (preferredApiName == noneApi.name){ // Since plugins are loaded in stages this function can get called multiple times.
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) // The issue with this is that the homepage may be fetched multiple times while the first request is loading
loadAndCancel(noneApi) val api = getApiFromNameNull(preferredApiName)
} if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) {
else if (preferredApiName == randomApi.name || api == null) { return@launchSafe
val validAPIs = context?.filterProviderByPreferredMedia() }
if (validAPIs.isNullOrEmpty()) { // If the plugin isn't loaded yet. (Does not set the key)
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded if (api == null) {
loadAndCancel(noneApi) loadAndCancel(noneApi)
} else { } else if (preferredApiName == noneApi.name) {
val apiRandom = validAPIs.random() setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(apiRandom) loadAndCancel(noneApi)
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) } 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
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)
} }
}
} }

View file

@ -6,6 +6,7 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.* import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource
@ -742,7 +743,15 @@ class CS3IPlayer : IPlayer {
} }
Player.STATE_ENDED -> { 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 -> { Player.STATE_BUFFERING -> {
updatedTime() updatedTime()
@ -780,7 +789,15 @@ class CS3IPlayer : IPlayer {
} }
Player.STATE_ENDED -> { 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 -> { Player.STATE_BUFFERING -> {
updatedTime() updatedTime()

View file

@ -29,6 +29,7 @@
<string name="double_tap_pause_enabled_key" translatable="false">double_tap_pause_enabled_key</string> <string name="double_tap_pause_enabled_key" translatable="false">double_tap_pause_enabled_key</string>
<string name="double_tap_seek_time_key" translatable="false">double_tap_seek_time_key</string> <string name="double_tap_seek_time_key" translatable="false">double_tap_seek_time_key</string>
<string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string> <string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string>
<string name="autoplay_next_key" translatable="false">autoplay_next_key</string>
<string name="display_sub_key" translatable="false">display_sub_key</string> <string name="display_sub_key" translatable="false">display_sub_key</string>
<string name="show_fillers_key" translatable="false">show_fillers_key</string> <string name="show_fillers_key" translatable="false">show_fillers_key</string>
<string name="show_trailers_key" translatable="false">show_trailers_key</string> <string name="show_trailers_key" translatable="false">show_trailers_key</string>
@ -42,7 +43,7 @@
<string name="primary_color_key" translatable="false">primary_color_key</string> <string name="primary_color_key" translatable="false">primary_color_key</string>
<string name="restore_key" translatable="false">restore_key</string> <string name="restore_key" translatable="false">restore_key</string>
<string name="backup_key" translatable="false">backup_key</string> <string name="backup_key" translatable="false">backup_key</string>
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key</string> <string name="prefer_media_type_key" translatable="false">prefer_media_type_key_2</string>
<string name="app_theme_key" translatable="false">app_theme_key</string> <string name="app_theme_key" translatable="false">app_theme_key</string>
<string name="episode_sync_enabled_key" translatable="false">episode_sync_enabled_key</string> <string name="episode_sync_enabled_key" translatable="false">episode_sync_enabled_key</string>
<string name="log_enabled_key" translatable="false">log_enabled_key</string> <string name="log_enabled_key" translatable="false">log_enabled_key</string>
@ -223,6 +224,10 @@
<string name="swipe_to_seek_settings_des">Swipe left or right to control time in the videoplayer</string> <string name="swipe_to_seek_settings_des">Swipe left or right to control time in the videoplayer</string>
<string name="swipe_to_change_settings">Swipe to change settings</string> <string name="swipe_to_change_settings">Swipe to change settings</string>
<string name="swipe_to_change_settings_des">Swipe on the left or right side to change brightness or volume</string> <string name="swipe_to_change_settings_des">Swipe on the left or right side to change brightness or volume</string>
<string name="autoplay_next_settings">Autoplay next episode</string>
<string name="autoplay_next_settings_des">Start the next episode when the current one ends</string>
<string name="double_tap_to_seek_settings">Double tap to seek</string> <string name="double_tap_to_seek_settings">Double tap to seek</string>
<string name="double_tap_to_pause_settings">Double tap to pause</string> <string name="double_tap_to_pause_settings">Double tap to pause</string>
<string name="double_tap_to_seek_amount_settings">Player seek amount</string> <string name="double_tap_to_seek_amount_settings">Player seek amount</string>

View file

@ -58,6 +58,12 @@
android:title="@string/swipe_to_change_settings" android:title="@string/swipe_to_change_settings"
android:summary="@string/swipe_to_change_settings_des" android:summary="@string/swipe_to_change_settings_des"
app:defaultValue="true" /> app:defaultValue="true" />
<SwitchPreference
android:icon="@drawable/ic_baseline_skip_next_24"
app:key="@string/autoplay_next_key"
android:title="@string/autoplay_next_settings"
android:summary="@string/autoplay_next_settings_des"
app:defaultValue="true" />
<SwitchPreference <SwitchPreference
android:icon="@drawable/ic_baseline_touch_app_24" android:icon="@drawable/ic_baseline_touch_app_24"
app:key="@string/double_tap_enabled_key" app:key="@string/double_tap_enabled_key"