diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 043195f5..216a5f21 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -24,6 +24,7 @@
+
diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
index 8f22c01a..32df314f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
@@ -10,16 +10,22 @@ import android.util.Log
import android.view.*
import android.widget.TextView
import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.MainThread
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastSession
+import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
+import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType
+import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
+import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
@@ -34,6 +40,7 @@ object CommonActivity {
return (this as MainActivity?)?.mSessionManager?.currentCastSession
}
+
var canEnterPipMode: Boolean = false
var canShowPipMode: Boolean = false
var isInPIPMode: Boolean = false
@@ -117,7 +124,7 @@ object CommonActivity {
setLocale(this, localeCode)
}
- fun init(act: Activity?) {
+ fun init(act: ComponentActivity?) {
if (act == null) return
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
//https://developer.android.com/guide/topics/ui/picture-in-picture
@@ -129,6 +136,22 @@ object CommonActivity {
act.updateLocale()
act.updateTv()
NewPipe.init(DownloaderTestImpl.getInstance())
+
+ for (resumeApp in resumeApps) {
+ resumeApp.launcher =
+ act.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ 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 de01ef9f..4c193f42 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
@@ -35,6 +35,8 @@ import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
+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
@@ -53,7 +55,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStri
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
@@ -67,10 +68,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadResult
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
@@ -95,18 +94,65 @@ import java.nio.charset.Charset
import kotlin.reflect.KClass
+//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/
+
+//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 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
-
-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"
-
+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
@@ -373,31 +419,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()
broadcastIntent.action = "restart_service"
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 1997edbf..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
@@ -56,6 +56,7 @@ 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)
@@ -71,12 +72,14 @@ class EpisodeAdapter(
* 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
}
}
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 9bf378eb..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
@@ -15,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
@@ -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
@@ -613,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(
@@ -627,7 +627,7 @@ class ResultViewModel2 : ViewModel() {
)
)
- AcraApplication.setKey(
+ setKey(
DataStore.getFolderName(
DOWNLOAD_EPISODE_CACHE,
parentId.toString()
@@ -956,12 +956,16 @@ class ResultViewModel2 : ViewModel() {
private fun launchActivity(
activity: Activity?,
- work: suspend (CoroutineScope.(Activity) -> Unit)
+ resumeApp: ResultResume,
+ id: Int? = null,
+ work: suspend (Intent.(Activity) -> Unit)
): Job? {
val act = activity ?: return null
return CoroutineScope(Dispatchers.IO).launch {
try {
- work(act)
+ resumeApp.launch(id) {
+ work(act)
+ }
} catch (t: Throwable) {
logError(t)
main {
@@ -981,14 +985,12 @@ class ResultViewModel2 : ViewModel() {
title: String?,
posterUrl: String?,
subtitles: List
- ) = launchActivity(activity) { act ->
- val shareVideo = Intent(Intent.ACTION_VIEW)
+ ) = launchActivity(activity, WEB_VIDEO) {
+ setDataAndType(Uri.parse(link.url), "video/*")
- shareVideo.setDataAndType(Uri.parse(link.url), "video/*")
- shareVideo.setPackage(WEB_VIDEO_CAST_PACKAGE)
- shareVideo.putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray())
- title?.let { shareVideo.putExtra("title", title) }
- posterUrl?.let { shareVideo.putExtra("poster", posterUrl) }
+ 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)
@@ -997,10 +999,27 @@ class ResultViewModel2 : ViewModel() {
putString(key, value)
}
}
- shareVideo.putExtra("android.media.intent.extra.HTTP_HEADERS", headers)
- shareVideo.putExtra("secure_uri", true)
+ putExtra("android.media.intent.extra.HTTP_HEADERS", headers)
+ putExtra("secure_uri", true)
+ }
- act.startActivity(shareVideo)
+ 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/
@@ -1011,18 +1030,16 @@ class ResultViewModel2 : ViewModel() {
resume: Boolean = true,
// if it is only a single link then resume works correctly
singleFile: Boolean? = null
- ) = launchActivity(activity) { act ->
- val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT)
+ ) = 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)
- 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)
val outputDir = act.cacheDir
if (singleFile ?: (data.links.size == 1)) {
- vlcIntent.setDataAndType(data.links.first().url.toUri(), "video/*")
+ setDataAndType(data.links.first().url.toUri(), "video/*")
} else {
val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir)
@@ -1037,7 +1054,7 @@ class ResultViewModel2 : ViewModel() {
}
outputFile.writeText(text)
- vlcIntent.setDataAndType(
+ setDataAndType(
FileProvider.getUriForFile(
act,
act.applicationContext.packageName + ".provider",
@@ -1051,33 +1068,14 @@ class ResultViewModel2 : ViewModel() {
} else {
1L
}
- vlcIntent.putExtra("from_start", !resume)
- vlcIntent.putExtra("position", position)
- //vlcIntent.putExtra("subtitles_location", data.subs.first().url)
- /*for (s in data.subs) {
- if (s.origin == SubtitleOrigin.URL) {
- try {
- val txt = app.get(s.url, s.headers).text
- val subtitleFile = File.createTempFile("subtitle1", ".srt", outputDir)
- subtitleFile.writeText(txt)
- println("Subtitles::::::${subtitleFile.path}")
- vlcIntent.putExtra("subtitles_location", FileProvider.getUriForFile(
- act,
- act.applicationContext.packageName + ".provider",
- subtitleFile
- ))
- break
- } catch (t : Throwable) {
- logError(t)
- }
- }
- }*/
- vlcIntent.component = VLC_COMPONENT
- act.setKey(VLC_LAST_ID_KEY, id)
- act.startActivityForResult(vlcIntent, VLC_REQUEST_CODE)
+ component = VLC_COMPONENT
+
+ putExtra("from_start", !resume)
+ putExtra("position", position)
}
+
fun handleAction(activity: Activity?, click: EpisodeClickEvent) =
viewModelScope.launchSafe {
handleEpisodeClickEvent(activity, click)
@@ -1098,6 +1096,11 @@ class ResultViewModel2 : ViewModel() {
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
)
)
@@ -1329,6 +1332,21 @@ class ResultViewModel2 : ViewModel() {
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 =
@@ -2107,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/res/values/array.xml b/app/src/main/res/values/array.xml
index e4c8eb78..3554beb2 100644
--- a/app/src/main/res/values/array.xml
+++ b/app/src/main/res/values/array.xml
@@ -36,6 +36,7 @@
- @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
@@ -43,6 +44,7 @@
- 1
- 2
+ - 5
- 4
- 3
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7940eca9..924e5eb9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -633,6 +633,7 @@
Preferred video player
Internal player
VLC
+ MPV
Web Video Cast
Browser
App not found