diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index cd3c2574..250734cd 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- - name: Report provider bug
+ - name: Request a new provider or report bug with an existing provider
url: https://github.com/recloudstream
- about: Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
+ about: Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
- name: Discord
url: https://discord.gg/5Hus6fM
about: Join our discord for faster support on smaller issues.
diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue_action.yml
index 9ca9ff04..79e7766c 100644
--- a/.github/workflows/issue_action.yml
+++ b/.github/workflows/issue_action.yml
@@ -2,7 +2,7 @@ name: Issue automatic actions
on:
issues:
- types: [opened, edited]
+ types: [opened]
jobs:
issue-moderator:
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 460a47ea..216a5f21 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -21,6 +21,12 @@
android:name="android.software.leanback"
android:required="false" />
+
+
+
+
+
+
+ val resultCode = result.resultCode
+ val data = result.data
+ if (resultCode == AppCompatActivity.RESULT_OK && data != null && resumeApp.position != null && resumeApp.duration != null) {
+ val pos = data.getLongExtra(resumeApp.position, -1L)
+ val dur = data.getLongExtra(resumeApp.duration, -1L)
+ if (dur > 0L && pos > 0L)
+ DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur)
+ removeKey(resumeApp.lastId)
+ ResultFragment.updateUI()
+ }
+ }
+ }
}
private fun Activity.enterPIPMode() {
@@ -167,7 +190,7 @@ object CommonActivity {
"Amoled" -> R.style.AmoledMode
"AmoledLight" -> R.style.AmoledModeLight
"Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
- R.style.MonetMode else R.style.AppTheme
+ R.style.MonetMode else R.style.AppTheme
else -> R.style.AppTheme
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index e6345f81..b1763966 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -10,7 +10,7 @@ import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
-import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
@@ -36,6 +36,8 @@ import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.AcraApplication.Companion.context
+import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
+import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.loadThemes
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
@@ -55,7 +57,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.githubA
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
-import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
@@ -71,10 +72,8 @@ import com.lagradost.cloudstream3.utils.BackupUtils.restorePromptGithub
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.getKey
-import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
-import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.IOnBackPressed
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
@@ -99,17 +98,65 @@ import java.nio.charset.Charset
import kotlin.reflect.KClass
-const val VLC_PACKAGE = "org.videolan.vlc"
-const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
-val VLC_COMPONENT: ComponentName =
- ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
-const val VLC_REQUEST_CODE = 42
+//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
+//https://wiki.videolan.org/Android_Player_Intents/
-const val VLC_FROM_START = -1
-const val VLC_FROM_PROGRESS = -2
-const val VLC_EXTRA_POSITION_OUT = "extra_position"
-const val VLC_EXTRA_DURATION_OUT = "extra_duration"
-const val VLC_LAST_ID_KEY = "vlc_last_open_id"
+//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904
+//https://mpv-android.github.io/mpv-android/intent.html
+
+// https://www.webvideocaster.com/integrations
+
+//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
+
+const val VLC_PACKAGE = "org.videolan.vlc"
+const val MPV_PACKAGE = "is.xyz.mpv"
+const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
+
+val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
+val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
+
+//TODO REFACTOR AF
+data class ResultResume(
+ val packageString: String,
+ val action: String = Intent.ACTION_VIEW,
+ val position: String? = null,
+ val duration: String? = null,
+ var launcher: ActivityResultLauncher? = null,
+) {
+ val lastId get() = "${packageString}_last_open_id"
+ suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
+ val intent = Intent(action)
+
+ if (id != null)
+ setKey(lastId, id)
+ else
+ removeKey(lastId)
+
+ intent.setPackage(packageString)
+ callback.invoke(intent)
+ launcher?.launch(intent)
+ }
+}
+
+val VLC = ResultResume(
+ VLC_PACKAGE,
+ "org.videolan.vlc.player.result",
+ "extra_position",
+ "extra_duration",
+)
+
+val MPV = ResultResume(
+ MPV_PACKAGE,
+ //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
+ position = "position",
+ duration = "duration",
+)
+
+val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
+
+val resumeApps = arrayOf(
+ VLC, MPV, WEB_VIDEO
+)
// Short name for requests client to make it nicer to use
@@ -376,31 +423,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- if (requestCode == VLC_REQUEST_CODE) {
- if (resultCode == RESULT_OK && data != null) {
- val pos: Long =
- data.getLongExtra(
- VLC_EXTRA_POSITION_OUT,
- -1
- ) //Last position in media when player exited
- val dur: Long =
- data.getLongExtra(
- VLC_EXTRA_DURATION_OUT,
- -1
- ) //Last position in media when player exited
- val id = getKey(VLC_LAST_ID_KEY)
- println("SET KEY $id at $pos / $dur")
- if (dur > 0 && pos > 0) {
- setViewPos(id, pos, dur)
- }
- removeKey(VLC_LAST_ID_KEY)
- ResultFragment.updateUI()
- }
- }
- super.onActivityResult(requestCode, resultCode, data)
- }
-
override fun onDestroy() {
val broadcastIntent = Intent()
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
new file mode 100644
index 00000000..18602664
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
@@ -0,0 +1,39 @@
+package com.lagradost.cloudstream3.extractors
+
+import android.util.Log
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.base64Decode
+import com.lagradost.cloudstream3.utils.*
+
+class AStreamHub : ExtractorApi() {
+ override val name = "AStreamHub"
+ override val mainUrl = "https://astreamhub.com"
+ override val requiresReferer = true
+
+ override suspend fun getUrl(url: String, referer: String?): List {
+ val sources = mutableListOf()
+ app.get(url).document.selectFirst("body > script").let { script ->
+ val text = script?.html() ?: ""
+ Log.i("Dev", "text => $text")
+ if (text.isNotBlank()) {
+ val m3link = "(?<=file:)(.*)(?=,)".toRegex().find(text)
+ ?.groupValues?.get(0)?.trim()?.trim('"') ?: ""
+ Log.i("Dev", "m3link => $m3link")
+ if (m3link.isNotBlank()) {
+ sources.add(
+ ExtractorLink(
+ name = name,
+ source = name,
+ url = m3link,
+ isM3u8 = true,
+ quality = Qualities.Unknown.value,
+ referer = referer ?: url
+ )
+ )
+ }
+ }
+ }
+ return sources
+ }
+
+}
\ No newline at end of file
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 bf7e694c..35e041be 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
@@ -28,6 +28,7 @@ import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@@ -123,6 +124,10 @@ object PluginManager {
val plugins = getPluginsOnline().filter {
!it.filePath.contains(repositoryPath)
}
+ val file = File(repositoryPath)
+ normalSafeApiCall {
+ if (file.exists()) file.deleteRecursively()
+ }
setKey(PLUGINS_KEY, plugins)
}
}
@@ -174,8 +179,16 @@ object PluginManager {
val onlineData: Pair,
) {
val isOutdated =
- onlineData.second.version != savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
+ onlineData.second.version > savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
+
+ fun validOnlineData(context: Context): Boolean {
+ return getPluginPath(
+ context,
+ savedData.internalName,
+ onlineData.first
+ ).absolutePath == savedData.filePath
+ }
}
// var allCurrentOutDatedPlugins: Set = emptySet()
@@ -225,6 +238,8 @@ object PluginManager {
.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
.map { onlineData ->
OnlinePluginData(savedData, onlineData)
+ }.filter {
+ it.validOnlineData(activity)
}
}.flatten().distinctBy { it.onlineData.second.url }
@@ -416,6 +431,18 @@ object PluginManager {
) + "." + name.hashCode()
}
+ /**
+ * This should not be changed as it is used to also detect if a plugin is installed!
+ **/
+ fun getPluginPath(
+ context: Context,
+ internalName: String,
+ repositoryUrl: String
+ ): File {
+ val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
+ val fileName = getPluginSanitizedFileName(internalName)
+ return File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
+ }
/**
* Used for fresh installs
@@ -426,9 +453,7 @@ object PluginManager {
internalName: String,
repositoryUrl: String
): Boolean {
- val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
- val fileName = getPluginSanitizedFileName(internalName)
- val file = File("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
+ val file = getPluginPath(activity, internalName, repositoryUrl)
downloadAndLoadPlugin(activity, pluginUrl, internalName, file)
return true
}
@@ -454,7 +479,13 @@ object PluginManager {
return loadPlugin(
activity,
newFile ?: return false,
- PluginData(internalName, pluginUrl, true, newFile.absolutePath, PLUGIN_VERSION_NOT_SET)
+ PluginData(
+ internalName,
+ pluginUrl,
+ true,
+ newFile.absolutePath,
+ PLUGIN_VERSION_NOT_SET
+ )
)
} catch (e: Exception) {
logError(e)
@@ -462,18 +493,13 @@ object PluginManager {
}
}
- /**
- * @param isFilePath will treat the pluginUrl as as the filepath instead of url
- * */
- suspend fun deletePlugin(pluginIdentifier: String, isFilePath: Boolean): Boolean {
- val data =
- (if (isFilePath) (getPluginsLocal() + getPluginsOnline()).firstOrNull { it.filePath == pluginIdentifier }
- else getPluginsOnline().firstOrNull { it.url == pluginIdentifier }) ?: return false
+ suspend fun deletePlugin(file: File): Boolean {
+ val list = (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
return try {
- if (File(data.filePath).delete()) {
- unloadPlugin(data.filePath)
- deletePluginData(data)
+ if (File(file.absolutePath).delete()) {
+ unloadPlugin(file.absolutePath)
+ list.forEach { deletePluginData(it) }
return true
}
false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
index be4f695f..2fc97477 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
@@ -244,7 +244,7 @@ class IndexSubtitleApi : AbstractSubApi {
} else {
document.select("div.my-3.p-3 div.media").mapNotNull { block ->
val name =
- block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString()
+ block.selectFirst("strong.d-block")?.text()?.trim().toString()
if (seasonNum!! > 0) {
if (isRightEps(name, seasonNum, epNum)) {
fixUrl(block.selectFirst("a")!!.attr("href"))
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 4620b11f..520b6b99 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
@@ -521,15 +521,12 @@ class HomeFragment : Fragment() {
}
}
- //Disable Random button, if its toggled off on settings
+ //Load value for toggling Random button. Hide at startup
context?.let {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
toggleRandomButton =
settingsManager.getBoolean(getString(R.string.random_button_key), false)
- home_random?.isVisible = toggleRandomButton
- if (!toggleRandomButton) {
- home_random?.visibility = View.GONE
- }
+ home_random?.visibility = View.GONE
}
observe(homeViewModel.apiName) { apiName ->
@@ -626,6 +623,7 @@ class HomeFragment : Fragment() {
home_loading_shimmer?.stopShimmer()
val d = data.value
+ val mutableListOfResponse = mutableListOf()
listHomepageItems.clear()
// println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}")
@@ -638,6 +636,11 @@ class HomeFragment : Fragment() {
home_loading_error?.isVisible = false
home_loaded?.isVisible = true
if (toggleRandomButton) {
+ //Flatten list
+ d.values.forEach { dlist ->
+ mutableListOfResponse.addAll(dlist.list.list)
+ }
+ listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url })
home_random?.isVisible = listHomepageItems.isNotEmpty()
} else {
home_random?.isGone = true
@@ -1031,4 +1034,4 @@ class HomeFragment : Fragment() {
}
}
}
-}
\ No newline at end of file
+}
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 6254cb9b..d8497876 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
@@ -276,9 +276,6 @@ class HomeViewModel : ViewModel() {
if (preferredApiName == noneApi.name) {
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(noneApi)
- // If the plugin isn't loaded yet. (Does not set the key)
- } else if (api == null) {
- loadAndCancel(noneApi)
} else if (preferredApiName == randomApi.name) {
val validAPIs = context?.filterProviderByPreferredMedia()
if (validAPIs.isNullOrEmpty()) {
@@ -289,6 +286,9 @@ class HomeViewModel : ViewModel() {
loadAndCancel(apiRandom)
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
}
+ // If the plugin isn't loaded yet. (Does not set the key)
+ } else if (api == null) {
+ loadAndCancel(noneApi)
} else {
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
loadAndCancel(api)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
index c69dc476..e20a6c7b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
@@ -58,6 +58,7 @@ import kotlinx.android.synthetic.main.player_select_source_and_subs.*
import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings
import kotlinx.android.synthetic.main.player_select_tracks.*
import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
class GeneratorPlayer : FullScreenPlayer() {
companion object {
@@ -115,10 +116,11 @@ class GeneratorPlayer : FullScreenPlayer() {
override fun onTracksInfoChanged() {
val tracks = player.getVideoTracks()
- player_tracks_btt?.isVisible = tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
+ player_tracks_btt?.isVisible =
+ tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
// Only set the preferred language if it is available.
// Otherwise it may give some users audio track init failed!
- if (tracks.allAudioTracks.any { it.language == preferredAudioTrackLanguage }){
+ if (tracks.allAudioTracks.any { it.language == preferredAudioTrackLanguage }) {
player.setPreferredAudioTrack(preferredAudioTrackLanguage)
}
}
@@ -602,8 +604,20 @@ class GeneratorPlayer : FullScreenPlayer() {
subtitleList.setItemChecked(subtitleIndex, true)
subtitleList.setOnItemClickListener { _, _, which, _ ->
- subtitleIndex = which
- subtitleList.setItemChecked(which, true)
+ if (which > currentSubtitles.size) {
+ // Since android TV is funky the setOnItemClickListener will be triggered
+ // instead of setOnClickListener when selecting. To override this we programmatically
+ // click the view when selecting an item outside the list.
+
+ // Cheeky way of getting the view at that position to click it
+ // to avoid keeping track of the various footers.
+ // getChildAt() gives null :(
+ val child = subtitleList.adapter.getView(which, null, subtitleList)
+ child?.performClick()
+ } else {
+ subtitleIndex = which
+ subtitleList.setItemChecked(which, true)
+ }
}
sourceDialog.cancel_btt?.setOnClickListener {
@@ -762,7 +776,8 @@ class GeneratorPlayer : FullScreenPlayer() {
ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
// audioArrayAdapter.add(ctx.getString(R.string.no_subtitles))
audioArrayAdapter.addAll(currentAudioTracks.mapIndexed { index, format ->
- format.label ?: format.language?.let { fromTwoLettersToLanguage(it) } ?: index.toString()
+ format.label ?: format.language?.let { fromTwoLettersToLanguage(it) }
+ ?: index.toString()
})
audioList.adapter = audioArrayAdapter
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
index 6fc81473..e9fbd5f9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
@@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
+import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -9,6 +10,7 @@ import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
+import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
@@ -53,6 +55,10 @@ const val ACTION_SHOW_DESCRIPTION = 15
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
+const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
+const val ACTION_PLAY_EPISODE_IN_MPV = 17
+
+
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter(
@@ -60,6 +66,25 @@ class EpisodeAdapter(
private val clickCallback: (EpisodeClickEvent) -> Unit,
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter() {
+ companion object {
+ /**
+ * @return ACTION_PLAY_EPISODE_IN_PLAYER, ACTION_PLAY_EPISODE_IN_BROWSER or ACTION_PLAY_EPISODE_IN_VLC_PLAYER depending on player settings.
+ * See array.xml/player_pref_values
+ **/
+ fun getPlayerAction(context: Context): Int {
+
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
+ return when (settingsManager.getInt(context.getString(R.string.player_pref_key), 1)) {
+ 1 -> ACTION_PLAY_EPISODE_IN_PLAYER
+ 2 -> ACTION_PLAY_EPISODE_IN_VLC_PLAYER
+ 3 -> ACTION_PLAY_EPISODE_IN_BROWSER
+ 4 -> ACTION_PLAY_EPISODE_IN_WEB_VIDEO
+ 5 -> ACTION_PLAY_EPISODE_IN_MPV
+ else -> ACTION_PLAY_EPISODE_IN_PLAYER
+ }
+ }
+ }
+
var cardList: MutableList = mutableListOf()
private val mBoundViewHolders: HashSet = HashSet()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
index 1ec2dd39..a173f1c1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
@@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
+import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.*
@@ -455,7 +456,8 @@ open class ResultFragment : ResultTrailerPlayer() {
val apiName: String,
val showFillers: Boolean,
val dubStatus: DubStatus,
- val start: AutoResume?
+ val start: AutoResume?,
+ val playerAction: Int
)
private fun getStoredData(context: Context): StoredData? {
@@ -469,6 +471,8 @@ open class ResultFragment : ResultTrailerPlayer() {
) DubStatus.Dubbed else DubStatus.Subbed
val startAction = arguments?.getInt(START_ACTION_BUNDLE)
+ val playerAction = getPlayerAction(context)
+
val start = startAction?.let { action ->
val startValue = arguments?.getInt(START_VALUE_BUNDLE)
val resumeEpisode = arguments?.getInt(EPISODE_BUNDLE)
@@ -483,7 +487,7 @@ open class ResultFragment : ResultTrailerPlayer() {
season = resumeSeason
)
}
- return StoredData(url, apiName, showFillers, dubStatus, start)
+ return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
}
private fun reloadViewModel(success: Boolean = false) {
@@ -774,7 +778,8 @@ open class ResultFragment : ResultTrailerPlayer() {
viewModel.handleAction(
activity,
EpisodeClickEvent(
- ACTION_PLAY_EPISODE_IN_PLAYER, value.result
+ storedData?.playerAction ?: ACTION_PLAY_EPISODE_IN_PLAYER,
+ value.result
)
)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
index 48919308..906b652d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
@@ -1,14 +1,13 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Activity
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
+import android.content.*
import android.net.Uri
+import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.core.content.FileProvider
+import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@@ -16,6 +15,7 @@ import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
+import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
@@ -33,6 +33,7 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.IGenerator
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData
+import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
@@ -43,7 +44,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
-import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
@@ -52,9 +52,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
-import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.navigate
-import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import kotlinx.coroutines.*
import java.io.File
import java.lang.Math.abs
@@ -615,7 +613,7 @@ class ResultViewModel2 : ViewModel() {
val src = "$DOWNLOAD_NAVIGATE_TO/$parentId" // url ?: return@let
// SET VISUAL KEYS
- AcraApplication.setKey(
+ setKey(
DOWNLOAD_HEADER_CACHE,
parentId.toString(),
VideoDownloadHelper.DownloadHeaderCached(
@@ -629,7 +627,7 @@ class ResultViewModel2 : ViewModel() {
)
)
- AcraApplication.setKey(
+ setKey(
DataStore.getFolderName(
DOWNLOAD_EPISODE_CACHE,
parentId.toString()
@@ -956,70 +954,155 @@ class ResultViewModel2 : ViewModel() {
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
}
- private fun playWithVlc(act: Activity?, data: LinkLoadingResult, id: Int) = ioSafe {
- if (act == null) return@ioSafe
- if (data.links.isEmpty()) {
- showToast(act, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
- return@ioSafe
+ private fun launchActivity(
+ activity: Activity?,
+ resumeApp: ResultResume,
+ id: Int? = null,
+ work: suspend (Intent.(Activity) -> Unit)
+ ): Job? {
+ val act = activity ?: return null
+ return CoroutineScope(Dispatchers.IO).launch {
+ try {
+ resumeApp.launch(id) {
+ work(act)
+ }
+ } catch (t: Throwable) {
+ logError(t)
+ main {
+ if (t is ActivityNotFoundException) {
+ showToast(activity, txt(R.string.app_not_found_error), Toast.LENGTH_LONG)
+ } else {
+ showToast(activity, t.toString(), Toast.LENGTH_LONG)
+ }
+ }
+ }
}
- try {
- if (!act.checkWrite()) {
- act.requestRW()
- if (act.checkWrite()) return@ioSafe
- }
+ }
- val outputDir = act.cacheDir
- val outputFile = withContext(Dispatchers.IO) {
- File.createTempFile("mirrorlist", ".m3u8", outputDir)
+ private fun playInWebVideo(
+ activity: Activity?,
+ link: ExtractorLink,
+ title: String?,
+ posterUrl: String?,
+ subtitles: List
+ ) = launchActivity(activity, WEB_VIDEO) {
+ setDataAndType(Uri.parse(link.url), "video/*")
+
+ putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray())
+ title?.let { putExtra("title", title) }
+ posterUrl?.let { putExtra("poster", posterUrl) }
+ val headers = Bundle().apply {
+ if (link.referer.isNotBlank())
+ putString("Referer", link.referer)
+ putString("User-Agent", USER_AGENT)
+ for ((key, value) in link.headers) {
+ putString(key, value)
}
+ }
+ putExtra("android.media.intent.extra.HTTP_HEADERS", headers)
+ putExtra("secure_uri", true)
+ }
+
+ private fun playWithMpv(
+ activity: Activity?,
+ id: Int,
+ link: ExtractorLink,
+ subtitles: List,
+ resume: Boolean = true,
+ ) = launchActivity(activity, MPV, id) {
+ putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray())
+ putExtra("subs.name", subtitles.map { it.name }.toTypedArray())
+ putExtra("subs.filename", subtitles.map { it.name }.toTypedArray())
+ setDataAndType(Uri.parse(link.url), "video/*")
+ component = MPV_COMPONENT
+ putExtra("secure_uri", true)
+ putExtra("return_result", true)
+ val position = getViewPos(id)?.position
+ if (resume && position != null)
+ putExtra("position", position.toInt())
+ }
+
+ // https://wiki.videolan.org/Android_Player_Intents/
+ private fun playWithVlc(
+ activity: Activity?,
+ data: LinkLoadingResult,
+ id: Int,
+ resume: Boolean = true,
+ // if it is only a single link then resume works correctly
+ singleFile: Boolean? = null
+ ) = launchActivity(activity, VLC, id) { act ->
+ addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
+ addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+
+ val outputDir = act.cacheDir
+
+ if (singleFile ?: (data.links.size == 1)) {
+ setDataAndType(data.links.first().url.toUri(), "video/*")
+ } else {
+ val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
+
var text = "#EXTM3U"
- for (sub in data.subs) {
- text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
- }
+
+ // With subtitles it doesn't work for no reason :(
+// for (sub in data.subs) {
+// text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
+// }
for (link in data.links) {
text += "\n#EXTINF:, ${link.name}\n${link.url}"
}
outputFile.writeText(text)
- val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
-
- vlcIntent.setPackage(VLC_PACKAGE)
- vlcIntent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
- vlcIntent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION)
- vlcIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- vlcIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
-
- vlcIntent.setDataAndType(
+ setDataAndType(
FileProvider.getUriForFile(
act,
act.applicationContext.packageName + ".provider",
outputFile
), "video/*"
)
-
- val startId = VLC_FROM_PROGRESS
-
- var position = startId
- if (startId == VLC_FROM_START) {
- position = 1
- } else if (startId == VLC_FROM_PROGRESS) {
- position = 0
- }
-
- vlcIntent.putExtra("position", position)
-
- vlcIntent.component = VLC_COMPONENT
- act.setKey(VLC_LAST_ID_KEY, id)
- act.startActivityForResult(vlcIntent, VLC_REQUEST_CODE)
- } catch (e: Exception) {
- logError(e)
- showToast(act, e.toString(), Toast.LENGTH_LONG)
}
+
+ val position = if (resume) {
+ getViewPos(id)?.position ?: 0L
+ } else {
+ 1L
+ }
+
+ component = VLC_COMPONENT
+
+ putExtra("from_start", !resume)
+ putExtra("position", position)
}
- fun handleAction(activity: Activity?, click: EpisodeClickEvent) = viewModelScope.launchSafe {
- handleEpisodeClickEvent(activity, click)
- }
+
+ fun handleAction(activity: Activity?, click: EpisodeClickEvent) =
+ viewModelScope.launchSafe {
+ handleEpisodeClickEvent(activity, click)
+ }
+
+ data class ExternalApp(
+ val packageString: String,
+ val name: Int,
+ val action: Int,
+ )
+
+ private val apps = listOf(
+ ExternalApp(
+ VLC_PACKAGE,
+ R.string.player_settings_play_in_vlc,
+ ACTION_PLAY_EPISODE_IN_VLC_PLAYER
+ ), ExternalApp(
+ WEB_VIDEO_CAST_PACKAGE,
+ R.string.player_settings_play_in_web,
+ ACTION_PLAY_EPISODE_IN_WEB_VIDEO
+ ),
+ ExternalApp(
+ MPV_PACKAGE,
+ R.string.player_settings_play_in_mpv,
+ ACTION_PLAY_EPISODE_IN_MPV
+ )
+ )
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
when (click.action) {
@@ -1035,9 +1118,17 @@ class ResultViewModel2 : ViewModel() {
}
options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER)
- if (activity?.isAppInstalled(VLC_PACKAGE) == true) {
- options.add(txt(R.string.episode_action_play_in_vlc) to ACTION_PLAY_EPISODE_IN_VLC_PLAYER)
+ for (app in apps) {
+ if (activity?.isAppInstalled(app.packageString) == true) {
+ options.add(
+ txt(
+ R.string.episode_action_play_in_format,
+ txt(app.name)
+ ) to app.action
+ )
+ }
}
+
options.addAll(
listOf(
txt(R.string.episode_action_play_in_browser) to ACTION_PLAY_EPISODE_IN_BROWSER,
@@ -1073,9 +1164,10 @@ class ResultViewModel2 : ViewModel() {
click.copy(action = ACTION_CHROME_CAST_EPISODE)
)
} else {
+ val action = getPlayerAction(ctx)
handleEpisodeClickEvent(
activity,
- click.copy(action = ACTION_PLAY_EPISODE_IN_PLAYER)
+ click.copy(action = action)
)
}
}
@@ -1212,6 +1304,11 @@ class ResultViewModel2 : ViewModel() {
}
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
+ if (links.links.isEmpty()) {
+ showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
+ return@loadLinks
+ }
+
playWithVlc(
activity,
links,
@@ -1219,6 +1316,37 @@ class ResultViewModel2 : ViewModel() {
)
}
}
+ ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
+ click.data,
+ isCasting = true,
+ txt(
+ R.string.episode_action_play_in_format,
+ txt(R.string.player_settings_play_in_web)
+ )
+ ) { (result, index) ->
+ playInWebVideo(
+ activity,
+ result.links[index],
+ click.data.name ?: click.data.headerName,
+ click.data.poster,
+ result.subs
+ )
+ }
+ ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
+ click.data,
+ isCasting = true,
+ txt(
+ R.string.episode_action_play_in_format,
+ txt(R.string.player_settings_play_in_mpv)
+ )
+ ) { (result, index) ->
+ playWithMpv(
+ activity,
+ click.data.id,
+ result.links[index],
+ result.subs
+ )
+ }
ACTION_PLAY_EPISODE_IN_PLAYER -> {
val data = currentResponse?.syncData?.toList() ?: emptyList()
val list =
@@ -1284,7 +1412,11 @@ class ResultViewModel2 : ViewModel() {
}, {
if (this !is AnimeLoadResponse) return@argamap
val map =
- Kitsu.getEpisodesDetails(getMalId(), getAniListId(), isResponseRequired = false)
+ Kitsu.getEpisodesDetails(
+ getMalId(),
+ getAniListId(),
+ isResponseRequired = false
+ )
if (map.isNullOrEmpty()) return@argamap
updateEpisodes = DubStatus.values().map { dubStatus ->
val current =
@@ -1304,8 +1436,10 @@ class ResultViewModel2 : ViewModel() {
val currentBack = this
this.description = this.description ?: node.description?.en
this.name = this.name ?: node.titles?.canonical
- this.episode = this.episode ?: node.num ?: episodeNumbers[index]
- this.posterUrl = this.posterUrl ?: node.thumbnail?.original?.url
+ this.episode =
+ this.episode ?: node.num ?: episodeNumbers[index]
+ this.posterUrl =
+ this.posterUrl ?: node.thumbnail?.original?.url
}
}
}
@@ -1592,7 +1726,9 @@ class ResultViewModel2 : ViewModel() {
val idIndex = ep.key.id
for ((index, i) in ep.value.withIndex()) {
val episode = i.episode ?: (index + 1)
- val id = mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000) ?: 0)
+ val id =
+ mainId + episode + idIndex * 1_000_000 + (i.season?.times(10_000)
+ ?: 0)
if (!existingEpisodes.contains(id)) {
existingEpisodes.add(id)
val seasonData = loadResponse.seasonNames.getSeason(i.season)
@@ -1888,7 +2024,10 @@ class ResultViewModel2 : ViewModel() {
if (ep.getWatchProgress() > 0.9) continue
handleAction(
activity,
- EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep)
+ EpisodeClickEvent(
+ getPlayerAction(activity),
+ ep
+ )
)
break
}
@@ -1905,7 +2044,10 @@ class ResultViewModel2 : ViewModel() {
?: return@launchSafe
handleAction(
activity,
- EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, episode)
+ EpisodeClickEvent(
+ getPlayerAction(activity),
+ episode
+ )
)
}
}
@@ -1983,7 +2125,7 @@ class ResultViewModel2 : ViewModel() {
preferStartEpisode = getResultEpisode(mainId)
preferStartSeason = getResultSeason(mainId)
- AcraApplication.setKey(
+ setKey(
DOWNLOAD_HEADER_CACHE,
mainId.toString(),
VideoDownloadHelper.DownloadHeaderCached(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt
index 125fadec..33d41934 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt
@@ -113,6 +113,22 @@ class SettingsPlayer : PreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
+ getPref(R.string.player_pref_key)?.setOnPreferenceClickListener {
+ val prefNames = resources.getStringArray(R.array.player_pref_names)
+ val prefValues = resources.getIntArray(R.array.player_pref_values)
+ val current = settingsManager.getInt(getString(R.string.player_pref_key), 1)
+
+ activity?.showBottomDialog(
+ prefNames.toList(),
+ prefValues.indexOf(current),
+ getString(R.string.player_pref),
+ true,
+ {}) {
+ settingsManager.edit().putInt(getString(R.string.player_pref_key), prefValues[it]).apply()
+ }
+ return@setOnPreferenceClickListener true
+ }
+
getPref(R.string.subtitle_settings_key)?.setOnPreferenceClickListener {
SubtitlesFragment.push(activity, false)
return@setOnPreferenceClickListener true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
index 7b944f62..e4435fff 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
@@ -147,7 +147,7 @@ class PluginsFragment : Fragment() {
pluginViewModel.updatePluginListLocal()
tv_types_scroll_view?.isVisible = false
} else {
- pluginViewModel.updatePluginList(url)
+ pluginViewModel.updatePluginList(context, url)
tv_types_scroll_view?.isVisible = true
// 💀💀💀💀💀💀💀 Recyclerview when
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
index 0a71c17a..6d94f91e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
@@ -1,6 +1,7 @@
package com.lagradost.cloudstream3.ui.settings.extensions
import android.app.Activity
+import android.content.Context
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.LiveData
@@ -13,6 +14,7 @@ import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.plugins.PluginData
import com.lagradost.cloudstream3.plugins.PluginManager
+import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath
import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.plugins.SitePlugin
import com.lagradost.cloudstream3.ui.result.txt
@@ -45,8 +47,8 @@ class PluginsViewModel : ViewModel() {
private val repositoryCache: MutableMap> = mutableMapOf()
const val TAG = "PLG"
- private fun isDownloaded(plugin: Plugin, data: Set? = null): Boolean {
- return (data ?: getDownloads()).contains(plugin.second.internalName)
+ private fun isDownloaded(context: Context, pluginName: String, repositoryUrl: String): Boolean {
+ return getPluginPath(context, pluginName, repositoryUrl).exists()
}
private suspend fun getPlugins(
@@ -63,24 +65,15 @@ class PluginsViewModel : ViewModel() {
?.also { repositoryCache[repositoryUrl] = it } ?: emptyList()
}
- private fun getStoredPlugins(): Array {
- return PluginManager.getPluginsOnline()
- }
-
- private fun getDownloads(): Set {
- return getStoredPlugins().map { it.internalName }.toSet()
- }
-
/**
* @param viewModel optional, updates the plugins livedata for that viewModel if included
* */
fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) =
ioSafe {
if (activity == null) return@ioSafe
- val stored = getDownloads()
val plugins = getPlugins(repositoryUrl)
- plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list ->
+ plugins.filter { plugin -> !isDownloaded(activity, plugin.second.internalName, repositoryUrl) }.also { list ->
main {
showToast(
activity,
@@ -103,7 +96,7 @@ class PluginsViewModel : ViewModel() {
PluginManager.downloadAndLoadPlugin(
activity,
metadata.url,
- metadata.name,
+ metadata.internalName,
repo
)
}.main { list ->
@@ -117,7 +110,7 @@ class PluginsViewModel : ViewModel() {
),
Toast.LENGTH_SHORT
)
- viewModel?.updatePluginListPrivate(repositoryUrl)
+ viewModel?.updatePluginListPrivate(activity, repositoryUrl)
} else if (list.isNotEmpty()) {
showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT)
}
@@ -140,11 +133,10 @@ class PluginsViewModel : ViewModel() {
if (activity == null) return@ioSafe
val (repo, metadata) = plugin
- val (success, message) = if (isDownloaded(plugin) || isLocal) {
- PluginManager.deletePlugin(
- metadata.url,
- isLocal
- ) to R.string.plugin_deleted
+ val file = getPluginPath(activity, plugin.second.internalName, plugin.first)
+
+ val (success, message) = if (file.exists() || isLocal) {
+ PluginManager.deletePlugin(file) to R.string.plugin_deleted
} else {
PluginManager.downloadAndLoadPlugin(
activity,
@@ -165,14 +157,13 @@ class PluginsViewModel : ViewModel() {
if (isLocal)
updatePluginListLocal()
else
- updatePluginListPrivate(repositoryUrl)
+ updatePluginListPrivate(activity, repositoryUrl)
}
- private suspend fun updatePluginListPrivate(repositoryUrl: String) {
- val stored = getDownloads()
+ private suspend fun updatePluginListPrivate(context: Context, repositoryUrl: String) {
val plugins = getPlugins(repositoryUrl)
val list = plugins.map { plugin ->
- PluginViewData(plugin, isDownloaded(plugin, stored))
+ PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first))
}
this.plugins = list
@@ -211,9 +202,10 @@ class PluginsViewModel : ViewModel() {
_filteredPlugins.postValue(false to plugins.filterTvTypes().filterLang().sortByQuery(currentQuery))
}
- fun updatePluginList(repositoryUrl: String) = viewModelScope.launchSafe {
+ fun updatePluginList(context: Context?, repositoryUrl: String) = viewModelScope.launchSafe {
+ if (context == null) return@launchSafe
Log.i(TAG, "updatePluginList = $repositoryUrl")
- updatePluginListPrivate(repositoryUrl)
+ updatePluginListPrivate(context, repositoryUrl)
}
fun search(query: String?) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
index b1d82b76..b97468e7 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
@@ -135,7 +135,8 @@ class SubtitlesFragment : Fragment() {
it.mkdir()
}
return fontDir.list()?.mapNotNull {
- if (it.endsWith(".ttf")) {
+ // No idea which formats are supported, but these should be.
+ if (it.endsWith(".ttf") || it.endsWith(".otf")) {
File(fontDir.absolutePath + "/" + it)
} else null
} ?: listOf()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
index b5c2cd44..199f0398 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
@@ -344,6 +344,7 @@ val extractorApis: MutableList = arrayListOf(
VidSrcExtractor(),
VidSrcExtractor2(),
PlayLtXyz(),
+ AStreamHub(),
)
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index 23b9af9b..3fb2e26c 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -274,7 +274,7 @@
حلقة كروم كاست
مرآة كروم كاست
تشغيل في التطبيق
- VLC تشغيل في
+ %s تشغيل في
تشغيل في الويب
نسخ الرابط
التحميل التلقائي
diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml
index d4cb4caa..bffaf804 100644
--- a/app/src/main/res/values-bp/strings.xml
+++ b/app/src/main/res/values-bp/strings.xml
@@ -279,7 +279,7 @@
Episódio pelo Chromecast
Alternativa pelo Chromecast
Assistir no App
- Assistir no VLC
+ Assistir no %s
Assistir no navegador
Copiar link
Auto download
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index aa840760..9e00f17b 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -268,7 +268,7 @@
Chromecastovat epizodu
Chromecast jako zrcadlo
Přehrát v aplikace
- Přehrát ve VLC
+ Přehrát ve %s
Přehrát v prohlížeči
Zkopírovat odkaz
Automaticky stáhnout
diff --git a/app/src/main/res/values-de/strings-de.xml b/app/src/main/res/values-de/strings-de.xml
index 621080b5..e1d657c7 100644
--- a/app/src/main/res/values-de/strings-de.xml
+++ b/app/src/main/res/values-de/strings-de.xml
@@ -281,7 +281,7 @@
Chromecast-Episode
Chromecastmirror
In App wiedergeben
- In VLC wiedergeben
+ In %s wiedergeben
In Browser wiedergeben
Link kopieren
Auto Download
diff --git a/app/src/main/res/values-es/array.xml b/app/src/main/res/values-es/array.xml
index ae986642..d1e5be2f 100644
--- a/app/src/main/res/values-es/array.xml
+++ b/app/src/main/res/values-es/array.xml
@@ -156,7 +156,7 @@
- @string/episode_action_chromecast_episode
- @string/episode_action_chromecast_mirror
- @string/episode_action_play_in_app
- - @string/episode_action_play_in_vlc
+ - @string/episode_action_play_in_format
- @string/episode_action_play_in_browser
- @string/episode_action_copy_link
- @string/episode_action_auto_download
diff --git a/app/src/main/res/values-es/strings-es.xml b/app/src/main/res/values-es/strings-es.xml
index 9b739b39..172d079c 100644
--- a/app/src/main/res/values-es/strings-es.xml
+++ b/app/src/main/res/values-es/strings-es.xml
@@ -269,7 +269,7 @@
Episodio Chromecast
Espejo Chromecast
Reproducir en la app
- Reproducir en VLC
+ Reproducir en %s
Reproducir en el navegador
Copiar enlace
Descarga automática
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 7d259b31..c98173ce 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -164,7 +164,7 @@
Episode Chromecast
Miroir Chromecast
Lecture dans l\'application
- Lecture dans VLC
+ Lecture dans %s
Lecture dans le navigateur
Copier le lien
Téléchargement Automatique
diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml
index e3f0a233..a6ad0af7 100644
--- a/app/src/main/res/values-hi/strings.xml
+++ b/app/src/main/res/values-hi/strings.xml
@@ -136,7 +136,7 @@
क्रोमकास्ट एपिसोड
कक्रोमकास्ट मिरर
एप्प मैं चलाये
- VLC में चलाए
+ %s में चलाए
Browser में चलाए
लिंक कॉपी करें
डाउनलोड करे
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index bc2a7098..2a7bff1c 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -299,7 +299,7 @@
Chromecast epizoda
Chromecast mirror
Pokreni u aplikaciji
- Pokreni u VLC-u
+ Pokreni u %s
Pokreni u pregledniku
Kopiraj poveznicu
Automatsko preuzimanje
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index e8f7e9e9..31b84bfd 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -264,7 +264,7 @@
Episode Chromecast
Mirror Chromecast
Putar di aplikasi
- Putar di VLC
+ Putar di %s
Putar di browser
Salin tautan
Download otomatis
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index db72b31d..86206213 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -271,7 +271,7 @@
Chromecast
Chromecast mirror
Riproduci in app
- Riproduci in VLC
+ Riproduci in %s
Riproduci nel browser
Copia link
Download
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 96044dc7..85aee997 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -190,7 +190,7 @@
Епизода на Chromecast
Огледало на Chromecastr
Пушти во апликацијата
- Пушти на VLC
+ Пушти на %s
Пушти на прелистувач
Копирај линк
Авто превземање
diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
index d8fab674..78f45e26 100644
--- a/app/src/main/res/values-ml/strings.xml
+++ b/app/src/main/res/values-ml/strings.xml
@@ -175,7 +175,7 @@
ആപ്പിൽ പ്ലേയ് ചെയ്യുക
- VLCയിൽ പ്ലേയ് ചെയ്യുക
+ %sയിൽ പ്ലേയ് ചെയ്യുക
ബ്രൗസറിൽ പ്ലേയ് ചെയ്യുക
ലിങ്ക് പകർത്തുക
ഡൌൺലോഡ് ചെയ്യൂ
diff --git a/app/src/main/res/values-mo/string.xml b/app/src/main/res/values-mo/string.xml
index 340428b9..361aaf56 100644
--- a/app/src/main/res/values-mo/string.xml
+++ b/app/src/main/res/values-mo/string.xml
@@ -145,7 +145,7 @@
aauugghhooo-ahah ohaaauugghh
aoohaaahhu ahouuhhh
ooo-ahahaauuh aaahhu
- ooo-ahah ohaauuh
+ ooo-ahah ohaauuh
ahoha ooo-ahahohoohah oooohh
aauugghhahhaauugghh
aaaghhoooohh aaahhu ahooo
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index a5afd785..7daca143 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -274,7 +274,7 @@
Chromecast aflevering
Chromecast mirror
Speel in app
- Speel in VLC
+ Speel in %s
Speel in browser
Kopieer link
Automatisch downloaden
diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml
index fc7cbbe2..ebd6ee49 100644
--- a/app/src/main/res/values-no/strings.xml
+++ b/app/src/main/res/values-no/strings.xml
@@ -196,7 +196,7 @@
Støpt Episode
Støpt Speil
Spill i appen
- Spill i VLC
+ Spill i %s
Spill i nettleseren
Kopier link
Automatisk nedlasting
diff --git a/app/src/main/res/values-pl/array.xml b/app/src/main/res/values-pl/array.xml
index 3fe30d95..30b6f4a1 100644
--- a/app/src/main/res/values-pl/array.xml
+++ b/app/src/main/res/values-pl/array.xml
@@ -165,7 +165,7 @@
- @string/episode_action_chromecast_episode
- @string/episode_action_chromecast_mirror
- @string/episode_action_play_in_app
- - @string/episode_action_play_in_vlc
+ - @string/episode_action_play_in_format
- @string/episode_action_play_in_browser
- @string/episode_action_copy_link
- @string/episode_action_auto_download
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index fdc89692..6da1cc8f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -252,7 +252,7 @@
Chromecast odcinka
Chromecast mirroru
Odtwórz w aplikacji
- Odtwórz w VLC
+ Odtwórz w %s
Odtwórz w przeglądarce
Kopiuj link
Automatyczne pobieranie
diff --git a/app/src/main/res/values-pt/strings-pt.xml b/app/src/main/res/values-pt/strings-pt.xml
index de70f746..375c3193 100644
--- a/app/src/main/res/values-pt/strings-pt.xml
+++ b/app/src/main/res/values-pt/strings-pt.xml
@@ -268,7 +268,7 @@
Episódio pelo Chromecast
Alternativa pelo Chromecast
Reproduzir na app
- Reproduzir no VLC
+ Reproduzir no %s
Reproduzir no navegador
Copiar link
Transferência Automática
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 7d1f3458..ce8f328c 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -267,7 +267,7 @@
Chromecast
Chromecast alternativ
Redă în Aplicație
- Redă în VLC
+ Redă în %s
Redă în Browser
Copiază link-ul
Auto-descărcare
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 3f3cead1..58ff060e 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -167,7 +167,7 @@
Chromecasta ett Avsnitt
Chromecasta en Länk
Spela upp i appen
- Spela upp i VLC
+ Spela upp i %s
Spela upp i webbläsaren
Kopiera länk
Automatisk nerladdning
diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml
index 9d0e0e02..dfe922ee 100644
--- a/app/src/main/res/values-tl/strings.xml
+++ b/app/src/main/res/values-tl/strings.xml
@@ -204,7 +204,7 @@
Chromecast Episode
Chromecast Mirror
I-play sa App
- I-play sa VLC
+ I-play sa %s
I-play sa browser
Kopyahin ang Link
Awtomatiking i-download
diff --git a/app/src/main/res/values-tr/array.xml b/app/src/main/res/values-tr/array.xml
index dbb17d36..177be03b 100644
--- a/app/src/main/res/values-tr/array.xml
+++ b/app/src/main/res/values-tr/array.xml
@@ -156,7 +156,7 @@
- @string/episode_action_chromecast_episode
- @string/episode_action_chromecast_mirror
- @string/episode_action_play_in_app
- - @string/episode_action_play_in_vlc
+ - @string/episode_action_play_in_format
- @string/episode_action_play_in_browser
- @string/episode_action_copy_link
- @string/episode_action_auto_download
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 14db2f6b..48e36013 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -272,7 +272,7 @@
Bölümü Chromecast ile yayınla
Bağlantıyı Chromecast ile yayınla
Uygulamada oynat
- VLC\'de oynat
+ %s\'de oynat
Tarayıcıda oynat
Linki kopyala
Otomatik indir
diff --git a/app/src/main/res/values-vi/array.xml b/app/src/main/res/values-vi/array.xml
index 5fee2d29..a5145c9e 100644
--- a/app/src/main/res/values-vi/array.xml
+++ b/app/src/main/res/values-vi/array.xml
@@ -157,7 +157,7 @@
- @string/episode_action_chromecast_episode
- @string/episode_action_chromecast_mirror
- @string/episode_action_play_in_app
- - @string/episode_action_play_in_vlc
+ - @string/episode_action_play_in_format
- @string/episode_action_play_in_browser
- @string/episode_action_copy_link
- @string/episode_action_auto_download
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index 302c13b5..ce8358cf 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -292,7 +292,7 @@
Tập Chromecast
Chiếu Chromecast
Xem với trình phát mặc định
- Xem với trình phát VLC
+ Xem với trình phát %s
Xem tại trình duyệt
Sao chép liên kết
Tự động tải xuống
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index d3ff6e1e..a6557990 100644
--- a/app/src/main/res/values-zh/strings.xml
+++ b/app/src/main/res/values-zh/strings.xml
@@ -303,7 +303,7 @@
投屏剧集
投屏镜像
在应用中播放
- 在 VLC 中播放
+ 在 %s 中播放
在浏览器中播放
复制链接
自动下载
diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml
index fedc4219..3554beb2 100644
--- a/app/src/main/res/values/array.xml
+++ b/app/src/main/res/values/array.xml
@@ -33,6 +33,22 @@
- 6
+
+ - @string/player_settings_play_in_app
+ - @string/player_settings_play_in_vlc
+ - @string/player_settings_play_in_mpv
+ - @string/player_settings_play_in_web
+ - @string/player_settings_play_in_browser
+
+
+
+ - 1
+ - 2
+ - 5
+ - 4
+ - 3
+
+
- @string/resolution_and_title
- @string/title
@@ -175,7 +191,7 @@
- @string/episode_action_chromecast_episode
- @string/episode_action_chromecast_mirror
- @string/episode_action_play_in_app
- - @string/episode_action_play_in_vlc
+ - @string/episode_action_play_in_format
- @string/episode_action_play_in_browser
- @string/episode_action_copy_link
- @string/episode_action_auto_download
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 511ab7ee..d33f81bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -14,6 +14,7 @@
subtitle_settings_key
subtitle_settings_chromecast_key
quality_pref_key
+ player_pref_key
prefer_limit_title_key
prefer_limit_title_rez_key
video_buffer_size_key
@@ -260,7 +261,7 @@
Search
Accounts
Updates and backup
-
+
Info
Advanced Search
Gives you the search results separated by provider
@@ -368,7 +369,7 @@
Chromecast episode
Chromecast mirror
Play in app
- Play in VLC
+ Play in %s
Play in browser
Copy link
Auto download
@@ -632,6 +633,14 @@
Supported
Language
Install the extension first
-
+
HLS Playlist
+
+ Preferred video player
+ Internal player
+ VLC
+ MPV
+ Web Video Cast
+ Browser
+ App not found
diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml
index a7e36488..6946a5c9 100644
--- a/app/src/main/res/xml/settings_player.xml
+++ b/app/src/main/res/xml/settings_player.xml
@@ -18,6 +18,11 @@
android:title="@string/watch_quality_pref"
android:icon="@drawable/ic_baseline_hd_24" />
+
+