From 6211b02e85b0dcd4ab5a2954623917e8c27ba552 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Sun, 3 Sep 2023 23:32:43 +0200 Subject: [PATCH] switched from isM3u8 to ExtractorLinkType --- .../cloudstream3/extractors/Pelisplus.kt | 3 +- .../cloudstream3/extractors/Vidstream.kt | 3 +- .../cloudstream3/extractors/WcoStream.kt | 4 +- .../cloudstream3/extractors/Wibufile.kt | 6 +- .../cloudstream3/ui/APIRepository.kt | 14 ++- .../cloudstream3/ui/ControllerActivity.kt | 4 +- .../cloudstream3/ui/player/CS3IPlayer.kt | 12 +- .../ui/player/DownloadFileGenerator.kt | 4 +- .../ui/player/ExtractorLinkGenerator.kt | 7 +- .../cloudstream3/ui/player/IGenerator.kt | 43 ++++++- .../cloudstream3/ui/player/LinkGenerator.kt | 2 +- .../ui/player/PlayerGeneratorViewModel.kt | 12 +- .../ui/player/RepoLinkGenerator.kt | 21 ++-- .../ui/result/ResultViewModel2.kt | 40 +++--- .../cloudstream3/utils/CastHelper.kt | 6 +- .../cloudstream3/utils/ExtractorApi.kt | 119 +++++++++++++++--- .../utils/VideoDownloadManager.kt | 78 +++++++----- 17 files changed, 269 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt index 45ec4c2f..4163cd94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.loadExtractor @@ -66,7 +67,7 @@ open class Pelisplus(val mainUrl: String) { href, page.url, getQualityFromName(qual), - element.attr("href").contains(".m3u8") + type = INFER_TYPE ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt index 7eb7fbac..c6493dbe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt @@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.argamap import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.loadExtractor @@ -70,7 +71,7 @@ class Vidstream(val mainUrl: String) { href, page.url, getQualityFromName(qual), - element.attr("href").contains(".m3u8") + type = INFER_TYPE ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index 6cc486cd..659d7804 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.extractors.helper.NineAnimeHelper.encrypt import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities class Vidstreamz : WcoStream() { @@ -126,8 +127,7 @@ open class WcoStream : ExtractorApi() { if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server") return response.parsed().data.media.sources.map { - ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8")) + ExtractorLink(name, it.file, it.file, host, Qualities.Unknown.value, type = INFER_TYPE) } - } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt index ae1e872a..c69f0938 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Wibufile.kt @@ -4,8 +4,8 @@ import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities -import java.net.URI open class Wibufile : ExtractorApi() { override val name: String = "Wibufile" @@ -28,10 +28,8 @@ open class Wibufile : ExtractorApi() { video ?: return, "$mainUrl/", Qualities.Unknown.value, - URI(url).path.endsWith(".m3u8") + type = INFER_TYPE ) ) - } - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 4ab2e8e2..a075cc2e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -1,16 +1,24 @@ package com.lagradost.cloudstream3.ui -import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.HomePageResponse +import com.lagradost.cloudstream3.LoadResponse +import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent +import com.lagradost.cloudstream3.MainPageRequest +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.fixUrl import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.async import kotlinx.coroutines.delay @@ -174,7 +182,7 @@ class APIRepository(val api: MainAPI) { data: String, isCasting: Boolean, subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit + callback: (ExtractorLink) -> Unit, ): Boolean { if (isInvalidData(data)) return false // this makes providers cleaner return try { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 46ddce09..6c0e7796 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.sortSubs import com.lagradost.cloudstream3.sortUrls +import com.lagradost.cloudstream3.ui.player.LoadType import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode @@ -294,7 +295,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val generator = RepoLinkGenerator(listOf(epData)) val isSuccessful = safeApiCall { - generator.generateLinks(clearCache = false, isCasting = true, + generator.generateLinks( + clearCache = false, type = LoadType.Chromecast, callback = { it.first?.let { link -> currentLinks.add(link) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 2067eb04..fd1da5ca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -53,9 +53,11 @@ import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File +import java.lang.IllegalArgumentException import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession @@ -1257,10 +1259,12 @@ class CS3IPlayer : IPlayer { HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) } - val mime = when { - link.isM3u8 -> MimeTypes.APPLICATION_M3U8 - link.isDash -> MimeTypes.APPLICATION_MPD - else -> MimeTypes.VIDEO_MP4 + val mime = when(link.type) { + ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8 + ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD + ExtractorLinkType.VIDEO -> MimeTypes.VIDEO_MP4 + ExtractorLinkType.TORRENT -> throw IllegalArgumentException("No torrent support") + ExtractorLinkType.MAGNET -> throw IllegalArgumentException("No magnet support") } val mediaItems = if (link is ExtractorLinkPlayList) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index 1b618e45..b0223bb5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -56,10 +56,10 @@ class DownloadFileGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int, + offset: Int ): Boolean { val meta = episodes[currentIndex + offset] callback(null to meta) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt index 7c19e97d..d8d2d537 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt @@ -37,14 +37,17 @@ class ExtractorLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int ): Boolean { subtitles.forEach(subtitleCallback) + val allowedTypes = type.toSet() links.forEach { - callback.invoke(it to null) + if(allowedTypes.contains(it.type)) { + callback.invoke(it to null) + } } return true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index a1287e6a..af74cb57 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -1,8 +1,43 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.ExtractorUri +enum class LoadType { + Unknown, + InApp, + InAppDownload, + ExternalApp, + Browser, + Chromecast +} + +fun LoadType.toSet() : Set { + return when(this) { + LoadType.InApp -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + LoadType.Browser -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + LoadType.InAppDownload -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.M3U8 + ) + LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.values().toSet() + LoadType.Chromecast -> setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + } +} + interface IGenerator { val hasCache: Boolean @@ -13,15 +48,15 @@ interface IGenerator { fun goto(index: Int) fun getCurrentId(): Int? // this is used to save data or read data about this id - fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null - fun getAll() : List? // this us used to get the metadata about all entries, not needed + fun getCurrent(offset: Int = 0): Any? // this is used to get metadata about the current playing, can return null + fun getAll(): List? // this us used to get the metadata about all entries, not needed /* not safe, must use try catch */ suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset : Int = 0, + offset: Int = 0, ): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 0b560857..ba2cdb40 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -48,7 +48,7 @@ class LinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 1b13b519..42659f8d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -78,10 +78,10 @@ class PlayerGeneratorViewModel : ViewModel() { if (generator?.hasCache == true && generator?.hasNext() == true) { safeApiCall { generator?.generateLinks( + type = LoadType.InApp, clearCache = false, - isCasting = false, - {}, - {}, + callback = {}, + subtitleCallback = {}, offset = 1 ) } @@ -147,7 +147,7 @@ class PlayerGeneratorViewModel : ViewModel() { } } - fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) { + fun loadLinks(clearCache: Boolean = false, type: LoadType = LoadType.InApp) { Log.i(TAG, "loadLinks") currentJob?.cancel() @@ -162,14 +162,14 @@ class PlayerGeneratorViewModel : ViewModel() { // load more data _loadingLinks.postValue(Resource.Loading()) val loadingState = safeApiCall { - generator?.generateLinks(clearCache = clearCache, isCasting = isCasting, { + generator?.generateLinks(type = type,clearCache = clearCache, callback = { currentLinks.add(it) // Clone to prevent ConcurrentModificationException normalSafeApiCall { // Extra normalSafeApiCall since .toSet() iterates. _currentLinks.postValue(currentLinks.toSet()) } - }, { + }, subtitleCallback = { currentSubs.add(it) normalSafeApiCall { _currentSubs.postValue(currentSubs.toSet()) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt index 2ce53ea5..d55da57c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt @@ -67,18 +67,19 @@ class RepoLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - isCasting: Boolean, + type: LoadType, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int, + offset: Int ): Boolean { + val allowedTypes = type.toSet() val index = currentIndex val current = episodes.getOrNull(index + offset) ?: return false val (currentLinkCache, currentSubsCache) = if (clearCache) { Pair(mutableSetOf(), mutableSetOf()) } else { - cache[Pair(current.apiName, current.id)] ?: Pair(mutableSetOf(), mutableSetOf()) + cache[current.apiName to current.id] ?: Pair(mutableSetOf(), mutableSetOf()) } //val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet() @@ -88,9 +89,9 @@ class RepoLinkGenerator( val currentSubsUrls = mutableSetOf() // makes all subs urls unique val currentSubsNames = mutableSetOf() // makes all subs names unique - currentLinkCache.forEach { link -> + currentLinkCache.filter { allowedTypes.contains(it.type) }.forEach { link -> currentLinks.add(link.url) - callback(Pair(link, null)) + callback(link to null) } currentSubsCache.forEach { sub -> @@ -108,8 +109,8 @@ class RepoLinkGenerator( val result = APIRepository( getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist") ).loadLinks(current.data, - isCasting, - { file -> + isCasting = LoadType.Chromecast == type, + subtitleCallback = { file -> val correctFile = PlayerSubtitleHelper.getSubtitleData(file) if (!currentSubsUrls.contains(correctFile.url)) { currentSubsUrls.add(correctFile.url) @@ -132,12 +133,14 @@ class RepoLinkGenerator( } } }, - { link -> + callback = { link -> Log.d(TAG, "Loaded ExtractorLink: $link") if (!currentLinks.contains(link.url)) { if (!currentLinkCache.contains(link)) { currentLinks.add(link.url) - callback(Pair(link, null)) + if (allowedTypes.contains(link.type)) { + callback(Pair(link, null)) + } currentLinkCache.add(link) //linkCache[index] = currentLinkCache } 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 82d9a8fe..b2c57137 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 @@ -36,6 +36,7 @@ import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator +import com.lagradost.cloudstream3.ui.player.LoadType import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction @@ -745,7 +746,7 @@ class ResultViewModel2 : ViewModel() { val generator = RepoLinkGenerator(listOf(episode)) val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, isCasting = false, callback = { + generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = { it.first?.let { link -> currentLinks.add(link) } @@ -825,7 +826,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean = true ) { if (activity == null) return - loadLinks(result, isVisible = isVisible, isCasting = true) { data -> + loadLinks(result, isVisible = isVisible, LoadType.Chromecast) { data -> startChromecast(activity, result, data.links, data.subs, 0) } } @@ -936,7 +937,7 @@ class ResultViewModel2 : ViewModel() { private fun loadLinks( result: ResultEpisode, isVisible: Boolean, - isCasting: Boolean, + type: LoadType, clearCache: Boolean = false, work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) ) { @@ -945,7 +946,7 @@ class ResultViewModel2 : ViewModel() { val links = loadLinks( result, isVisible = isVisible, - isCasting = isCasting, + type = type, clearCache = clearCache ) if (!this.isActive) return@ioSafe @@ -956,11 +957,11 @@ class ResultViewModel2 : ViewModel() { private var currentLoadLinkJob: Job? = null private fun acquireSingleLink( result: ResultEpisode, - isCasting: Boolean, + type: LoadType, text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + loadLinks(result, isVisible = true, type) { links -> postPopup( text, links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) { @@ -971,11 +972,10 @@ class ResultViewModel2 : ViewModel() { private fun acquireSingleSubtitle( result: ResultEpisode, - isCasting: Boolean, text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, isCasting = isCasting) { links -> + loadLinks(result, isVisible = true, type = LoadType.Unknown) { links -> postPopup( text, links.subs.map { txt(it.name) }) @@ -988,7 +988,7 @@ class ResultViewModel2 : ViewModel() { private suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, - isCasting: Boolean, + type: LoadType, clearCache: Boolean = false, ): LinkLoadingResult { val tempGenerator = RepoLinkGenerator(listOf(result)) @@ -1002,7 +1002,7 @@ class ResultViewModel2 : ViewModel() { } try { updatePage() - tempGenerator.generateLinks(clearCache, isCasting, { (link, _) -> + tempGenerator.generateLinks(clearCache, type, { (link, _) -> if (link != null) { links += link updatePage() @@ -1272,7 +1272,6 @@ class ResultViewModel2 : ViewModel() { acquireSingleSubtitle( click.data, - false, txt(R.string.episode_action_download_subtitle) ) { (links, index) -> downloadSubtitle( @@ -1317,7 +1316,7 @@ class ResultViewModel2 : ViewModel() { val response = currentResponse ?: return acquireSingleLink( click.data, - false, + LoadType.InAppDownload, txt(R.string.episode_action_download_mirror) ) { (result, index) -> ioSafe { @@ -1347,7 +1346,7 @@ class ResultViewModel2 : ViewModel() { loadLinks( click.data, isVisible = false, - isCasting = false, + type = LoadType.InApp, clearCache = true ) } @@ -1356,7 +1355,7 @@ class ResultViewModel2 : ViewModel() { ACTION_CHROME_CAST_MIRROR -> { acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt(R.string.episode_action_chromecast_mirror) ) { (result, index) -> startChromecast(activity, click.data, result.links, result.subs, index) @@ -1365,7 +1364,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Browser, txt(R.string.episode_action_play_in_browser) ) { (result, index) -> try { @@ -1380,7 +1379,7 @@ class ResultViewModel2 : ViewModel() { ACTION_COPY_LINK -> { acquireSingleLink( click.data, - isCasting = true, + LoadType.ExternalApp, txt(R.string.episode_action_copy_link) ) { (result, index) -> val act = activity ?: return@acquireSingleLink @@ -1399,7 +1398,7 @@ class ResultViewModel2 : ViewModel() { } ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - loadLinks(click.data, isVisible = true, isCasting = true) { links -> + loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> if (links.links.isEmpty()) { showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) return@loadLinks @@ -1415,7 +1414,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt( R.string.episode_action_play_in_format, txt(R.string.player_settings_play_in_web) @@ -1432,7 +1431,7 @@ class ResultViewModel2 : ViewModel() { ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink( click.data, - isCasting = true, + LoadType.Chromecast, txt( R.string.episode_action_play_in_format, txt(R.string.player_settings_play_in_mpv) @@ -1461,7 +1460,6 @@ class ResultViewModel2 : ViewModel() { if (index >= 0) it.goto(index) } - } ?: return, list ) ) @@ -2173,7 +2171,7 @@ class ResultViewModel2 : ViewModel() { trailerData.extractorUrl, trailerData.referer ?: "", Qualities.Unknown.value, - trailerData.extractorUrl.contains(".m3u8") + type = INFER_TYPE ) ) to arrayListOf() } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index 6b5e9ec2..d8373165 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -55,7 +55,11 @@ object CastHelper { val builder = MediaInfo.Builder(link.url) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) - .setContentType(if (link.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.VIDEO_MP4) + .setContentType(when(link.type) { + ExtractorLinkType.M3U8 -> MimeTypes.APPLICATION_M3U8 + ExtractorLinkType.DASH -> MimeTypes.APPLICATION_MPD + else -> MimeTypes.VIDEO_MP4 + }) .setMetadata(movieMetadata) .setMediaTracks(tracks) data?.let { 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 ffda32d7..2a539f0d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -4,8 +4,10 @@ import android.net.Uri import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.extractors.* +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import kotlinx.coroutines.delay import org.jsoup.Jsoup +import java.net.URL import kotlin.collections.MutableList /** @@ -35,35 +37,101 @@ data class ExtractorLinkPlayList( val playlist: List, override val referer: String, override val quality: Int, - override val isM3u8: Boolean = false, + val isM3u8: Boolean = false, override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ override val extractorData: String? = null, + override val type: ExtractorLinkType, ) : ExtractorLink( - source, - name, - // Blank as un-used - "", - referer, - quality, - isM3u8, - headers, - extractorData -) + source = source, + name = name, + url = "", + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = type +) { + constructor( + source: String, + name: String, + playlist: List, + referer: String, + quality: Int, + isM3u8: Boolean = false, + headers: Map = mapOf(), + extractorData: String? = null, + ) : this( + source = source, + name = name, + playlist = playlist, + referer = referer, + quality = quality, + type = if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO, + headers = headers, + extractorData = extractorData, + ) +} +/** Metadata about the file type used for downloads and exoplayer hint, + * if you respond with the wrong one the file will fail to download or be played */ +enum class ExtractorLinkType { + /** Single stream of bytes no matter the actual file type */ + VIDEO, + /** Split into several .ts files, has support for encrypted m3u8s */ + M3U8, + /** Like m3u8 but uses xml, currently no download support */ + DASH, + /** No support at the moment */ + TORRENT, + /** No support at the moment */ + MAGNET, +} +private fun inferTypeFromUrl(url: String): ExtractorLinkType { + val path = normalSafeApiCall { URL(url).path } + return when { + path?.endsWith(".m3u8") == true -> ExtractorLinkType.M3U8 + path?.endsWith(".mpd") == true -> ExtractorLinkType.DASH + path?.endsWith(".torrent") == true -> ExtractorLinkType.TORRENT + url.startsWith("magnet:") -> ExtractorLinkType.MAGNET + else -> ExtractorLinkType.VIDEO + } +} +val INFER_TYPE : ExtractorLinkType? = null open class ExtractorLink constructor( open val source: String, open val name: String, override val url: String, override val referer: String, open val quality: Int, - open val isM3u8: Boolean = false, override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ open val extractorData: String? = null, - open val isDash: Boolean = false, + open val type: ExtractorLinkType, ) : VideoDownloadManager.IDownloadableMinimum { + constructor( + source: String, + name: String, + url: String, + referer: String, + quality: Int, + /** the type of the media, use INFER_TYPE if you want to auto infer the type from the url */ + type: ExtractorLinkType?, + headers: Map = mapOf(), + /** Used for getExtractorVerifierJob() */ + extractorData: String? = null, + ) : this( + source = source, + name = name, + url = url, + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = type ?: inferTypeFromUrl(url) + ) + /** * Old constructor without isDash, allows for backwards compatibility with extensions. * Should be removed after all extensions have updated their cloudstream.jar @@ -80,8 +148,30 @@ open class ExtractorLink constructor( extractorData: String? = null ) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false) + constructor( + source: String, + name: String, + url: String, + referer: String, + quality: Int, + isM3u8: Boolean = false, + headers: Map = mapOf(), + /** Used for getExtractorVerifierJob() */ + extractorData: String? = null, + isDash: Boolean, + ) : this( + source = source, + name = name, + url = url, + referer = referer, + quality = quality, + headers = headers, + extractorData = extractorData, + type = if (isDash) ExtractorLinkType.DASH else if (isM3u8) ExtractorLinkType.M3U8 else ExtractorLinkType.VIDEO + ) + override fun toString(): String { - return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)" + return "ExtractorLink(name=$name, url=$url, referer=$referer, type=$type)" } } @@ -135,6 +225,7 @@ enum class Qualities(var value: Int, val defaultPriority: Int) { else -> "${qual}p" } } + fun getStringByIntFull(quality: Int): String { return when (quality) { 0 -> "Auto" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 72eb002a..d108daed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -53,7 +53,7 @@ import java.io.Closeable import java.io.File import java.io.IOException import java.io.OutputStream -import java.net.URL +import java.lang.IllegalArgumentException import java.util.* const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" @@ -951,7 +951,10 @@ object VideoDownloadManager { /** how many bytes every connection should be, by default it is 10 MiB */ chuckSize: Long = (1 shl 20) * 10, /** maximum bytes in the buffer that responds */ - bufferSize: Int = DEFAULT_BUFFER_SIZE + bufferSize: Int = DEFAULT_BUFFER_SIZE, + /** how many bytes bytes it should require to use the parallel downloader instead, + * if we download a very small file we don't want it parallel */ + maximumSmallSize : Long = chuckSize * 2 ): LazyStreamDownloadData { // we don't want to make a separate connection for every 1kb require(chuckSize > 1000) @@ -963,7 +966,7 @@ object VideoDownloadManager { var downloadLength: Long? = null var totalLength: Long? = null - val ranges = if (contentLength == null) { + val ranges = if (contentLength == null || contentLength < maximumSmallSize) { // is the equivalent of [startByte..EOF] as we don't know the size we can only do one // connection LongArray(1) { startByte } @@ -1024,6 +1027,7 @@ object VideoDownloadManager { } } + /** download a file that consist of a single stream of data*/ suspend fun downloadThing( context: Context, link: IDownloadableMinimum, @@ -1035,8 +1039,7 @@ object VideoDownloadManager { createNotificationCallback: (CreateNotificationMetadata) -> Unit, parallelConnections: Int = 3 ): DownloadStatus = withContext(Dispatchers.IO) { - // we cant download torrents with this implementation, aria2c might be used in the future - if (link.url.startsWith("magnet") || link.url.endsWith(".torrent") || parallelConnections < 1) { + if (parallelConnections < 1) { return@withContext DOWNLOAD_INVALID_INPUT } @@ -1529,6 +1532,11 @@ object VideoDownloadManager { notificationCallback: (Int, Notification) -> Unit, tryResume: Boolean = false, ): DownloadStatus { + // no support for these file formats + if(link.type == ExtractorLinkType.MAGNET || link.type == ExtractorLinkType.TORRENT || link.type == ExtractorLinkType.DASH) { + return DOWNLOAD_INVALID_INPUT + } + val name = getFileName(context, ep) // Make sure this is cancelled when download is done or cancelled. @@ -1557,35 +1565,39 @@ object VideoDownloadManager { } try { - if (link.isM3u8 || normalSafeApiCall { URL(link.url).path.endsWith(".m3u8") } == true) { - val startIndex = if (tryResume) { - context.getKey( - KEY_DOWNLOAD_INFO, - ep.id.toString(), - null - )?.extraInfo?.toIntOrNull() - } else null + when(link.type) { + ExtractorLinkType.M3U8 -> { + val startIndex = if (tryResume) { + context.getKey( + KEY_DOWNLOAD_INFO, + ep.id.toString(), + null + )?.extraInfo?.toIntOrNull() + } else null - return downloadHLS( - context, - link, - name, - folder ?: "", - ep.id, - startIndex, - callback, parallelConnections = maxConcurrentConnections - ) - } else { - return downloadThing( - context, - link, - name, - folder ?: "", - "mp4", - tryResume, - ep.id, - callback, parallelConnections = maxConcurrentConnections - ) + return downloadHLS( + context, + link, + name, + folder ?: "", + ep.id, + startIndex, + callback, parallelConnections = maxConcurrentConnections + ) + } + ExtractorLinkType.VIDEO -> { + return downloadThing( + context, + link, + name, + folder ?: "", + "mp4", + tryResume, + ep.id, + callback, parallelConnections = maxConcurrentConnections + ) + } + else -> throw IllegalArgumentException("unsuported download type") } } catch (t: Throwable) { return DOWNLOAD_FAILED