mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
switched from isM3u8 to ExtractorLinkType
This commit is contained in:
parent
9c991f2abd
commit
6211b02e85
17 changed files with 269 additions and 109 deletions
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<Response>().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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -56,10 +56,10 @@ class DownloadFileGenerator(
|
|||
|
||||
override suspend fun generateLinks(
|
||||
clearCache: Boolean,
|
||||
isCasting: Boolean,
|
||||
type: LoadType,
|
||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||
subtitleCallback: (SubtitleData) -> Unit,
|
||||
offset: Int,
|
||||
offset: Int
|
||||
): Boolean {
|
||||
val meta = episodes[currentIndex + offset]
|
||||
callback(null to meta)
|
||||
|
|
|
@ -37,14 +37,17 @@ class ExtractorLinkGenerator(
|
|||
|
||||
override suspend fun generateLinks(
|
||||
clearCache: Boolean,
|
||||
isCasting: Boolean,
|
||||
type: LoadType,
|
||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> 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
|
||||
|
|
|
@ -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<ExtractorLinkType> {
|
||||
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<Any>? // 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<Any>? // 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<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||
subtitleCallback: (SubtitleData) -> Unit,
|
||||
offset : Int = 0,
|
||||
offset: Int = 0,
|
||||
): Boolean
|
||||
}
|
|
@ -48,7 +48,7 @@ class LinkGenerator(
|
|||
|
||||
override suspend fun generateLinks(
|
||||
clearCache: Boolean,
|
||||
isCasting: Boolean,
|
||||
type: LoadType,
|
||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
|
||||
subtitleCallback: (SubtitleData) -> Unit,
|
||||
offset: Int
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -67,18 +67,19 @@ class RepoLinkGenerator(
|
|||
|
||||
override suspend fun generateLinks(
|
||||
clearCache: Boolean,
|
||||
isCasting: Boolean,
|
||||
type: LoadType,
|
||||
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> 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<String>() // makes all subs urls unique
|
||||
val currentSubsNames = mutableSetOf<String>() // 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
|
||||
}
|
||||
|
|
|
@ -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<ExtractorLink>()
|
||||
val currentSubs = mutableSetOf<SubtitleData>()
|
||||
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<LinkLoadingResult, Int>) -> 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<LinkLoadingResult, Int>) -> 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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<PlayListItem>,
|
||||
override val referer: String,
|
||||
override val quality: Int,
|
||||
override val isM3u8: Boolean = false,
|
||||
val isM3u8: Boolean = false,
|
||||
override val headers: Map<String, String> = 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<PlayListItem>,
|
||||
referer: String,
|
||||
quality: Int,
|
||||
isM3u8: Boolean = false,
|
||||
headers: Map<String, String> = 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<String, String> = 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<String, String> = 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<String, String> = 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"
|
||||
|
|
|
@ -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<DownloadedFileInfo>(
|
||||
KEY_DOWNLOAD_INFO,
|
||||
ep.id.toString(),
|
||||
null
|
||||
)?.extraInfo?.toIntOrNull()
|
||||
} else null
|
||||
when(link.type) {
|
||||
ExtractorLinkType.M3U8 -> {
|
||||
val startIndex = if (tryResume) {
|
||||
context.getKey<DownloadedFileInfo>(
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue