Merge remote-tracking branch 'origin/master'

This commit is contained in:
Sofie99 2023-09-07 21:55:28 +07:00
commit 6a303919e7
18 changed files with 618 additions and 123 deletions

View file

@ -51,12 +51,12 @@ android {
}
compileSdk = 33
buildToolsVersion = "30.0.3"
buildToolsVersion = "34.0.0"
defaultConfig {
applicationId = "com.lagradost.cloudstream3"
minSdk = 21
targetSdk = 29
targetSdk = 33
versionCode = 59
versionName = "4.1.8"

View file

@ -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
)
)
}

View file

@ -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
)
)
}

View file

@ -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)
}
}
}

View file

@ -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
)
)
}
}

View file

@ -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 {

View file

@ -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)

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint
import android.content.Context
import android.net.Uri
import android.os.Handler
@ -7,6 +8,7 @@ import android.os.Looper
import android.util.Log
import android.util.Rational
import android.widget.FrameLayout
import androidx.media3.common.C
import androidx.media3.common.C.*
import androidx.media3.common.Format
import androidx.media3.common.MediaItem
@ -31,6 +33,10 @@ import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.SeekParameters
import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager
import androidx.media3.exoplayer.drm.FrameworkMediaDrm
import androidx.media3.exoplayer.drm.LocalMediaDrmCallback
import androidx.media3.exoplayer.source.ClippingMediaSource
import androidx.media3.exoplayer.source.ConcatenatingMediaSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
@ -50,12 +56,16 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.DrmExtractorLink
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 java.util.UUID
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
@ -102,7 +112,16 @@ class CS3IPlayer : IPlayer {
* */
data class MediaItemSlice(
val mediaItem: MediaItem,
val durationUs: Long
val durationUs: Long,
val drm: DrmMetadata? = null
)
data class DrmMetadata(
val kid: String,
val key: String,
val uuid: UUID,
val kty: String,
val keyRequestParameters: HashMap<String, String>,
)
override fun getDuration(): Long? = exoPlayer?.duration
@ -338,6 +357,7 @@ class CS3IPlayer : IPlayer {
}.flatten()
}
@SuppressLint("UnsafeOptInUsageError")
private fun Tracks.Group.getFormats(): List<Pair<Format, Int>> {
return (0 until this.mediaTrackGroup.length).mapNotNull { i ->
if (this.isSupported)
@ -366,6 +386,7 @@ class CS3IPlayer : IPlayer {
)
}
@SuppressLint("UnsafeOptInUsageError")
override fun getVideoTracks(): CurrentTracks {
val allTracks = exoPlayer?.currentTracks?.groups ?: emptyList()
val videoTracks = allTracks.filter { it.type == TRACK_TYPE_VIDEO }
@ -385,6 +406,7 @@ class CS3IPlayer : IPlayer {
/**
* @return True if the player should be reloaded
* */
@SuppressLint("UnsafeOptInUsageError")
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
Log.i(TAG, "setPreferredSubtitles init $subtitle")
currentSubtitles = subtitle
@ -463,6 +485,7 @@ class CS3IPlayer : IPlayer {
}
}
@SuppressLint("UnsafeOptInUsageError")
override fun getAspectRatio(): Rational? {
return exoPlayer?.videoFormat?.let { format ->
Rational(format.width, format.height)
@ -473,6 +496,7 @@ class CS3IPlayer : IPlayer {
subtitleHelper.setSubStyle(style)
}
@SuppressLint("UnsafeOptInUsageError")
override fun saveData() {
Log.i(TAG, "saveData")
updatedTime()
@ -546,6 +570,7 @@ class CS3IPlayer : IPlayer {
var requestSubtitleUpdate: (() -> Unit)? = null
@SuppressLint("UnsafeOptInUsageError")
private fun createOnlineSource(headers: Map<String, String>): HttpDataSource.Factory {
val source = OkHttpDataSource.Factory(app.baseClient).setUserAgent(USER_AGENT)
return source.apply {
@ -553,6 +578,7 @@ class CS3IPlayer : IPlayer {
}
}
@SuppressLint("UnsafeOptInUsageError")
private fun createOnlineSource(link: ExtractorLink): HttpDataSource.Factory {
val provider = getApiFromNameNull(link.source)
val interceptor = provider?.getVideoInterceptor(link)
@ -630,6 +656,7 @@ class CS3IPlayer : IPlayer {
return Pair(subSources, activeSubtitles)
}*/
@SuppressLint("UnsafeOptInUsageError")
private fun getCache(context: Context, cacheSize: Long): SimpleCache? {
return try {
val databaseProvider = StandaloneDatabaseProvider(context)
@ -661,6 +688,7 @@ class CS3IPlayer : IPlayer {
return getMediaItemBuilder(mimeType).setUri(url).build()
}
@SuppressLint("UnsafeOptInUsageError")
private fun getTrackSelector(context: Context, maxVideoHeight: Int?): TrackSelector {
val trackSelector = DefaultTrackSelector(context)
trackSelector.parameters = trackSelector.buildUponParameters()
@ -674,6 +702,7 @@ class CS3IPlayer : IPlayer {
var currentTextRenderer: CustomTextRenderer? = null
@SuppressLint("UnsafeOptInUsageError")
private fun buildExoPlayer(
context: Context,
mediaItemSlices: List<MediaItemSlice>,
@ -758,15 +787,33 @@ class CS3IPlayer : IPlayer {
// If there is only one item then treat it as normal, if multiple: concatenate the items.
val videoMediaSource = if (mediaItemSlices.size == 1) {
factory.createMediaSource(mediaItemSlices.first().mediaItem)
val item = mediaItemSlices.first()
item.drm?.let { drm ->
val drmCallback =
LocalMediaDrmCallback("{\"keys\":[{\"kty\":\"${drm.kty}\",\"k\":\"${drm.key}\",\"kid\":\"${drm.kid}\"}],\"type\":\"temporary\"}".toByteArray())
val manager = DefaultDrmSessionManager.Builder()
.setPlayClearSamplesWithoutKeys(true)
.setMultiSession(false)
.setKeyRequestParameters(drm.keyRequestParameters)
.setUuidAndExoMediaDrmProvider(drm.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback)
val manifestDataSourceFactory = DefaultHttpDataSource.Factory()
DashMediaSource.Factory(manifestDataSourceFactory)
.setDrmSessionManagerProvider { manager }
.createMediaSource(item.mediaItem)
} ?: run {
factory.createMediaSource(item.mediaItem)
}
} else {
val source = ConcatenatingMediaSource()
mediaItemSlices.map {
mediaItemSlices.map { item ->
source.addMediaSource(
// The duration MUST be known for it to work properly, see https://github.com/google/ExoPlayer/issues/4727
ClippingMediaSource(
factory.createMediaSource(it.mediaItem),
it.durationUs
factory.createMediaSource(item.mediaItem),
item.durationUs
)
)
}
@ -1103,6 +1150,8 @@ class CS3IPlayer : IPlayer {
}
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
@SuppressLint("UnsafeOptInUsageError")
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
lastTimeStamps = timeStamps
timeStamps.forEach { timestamp ->
@ -1120,6 +1169,7 @@ class CS3IPlayer : IPlayer {
updatedTime()
}
@SuppressLint("UnsafeOptInUsageError")
fun onRenderFirst() {
if (hasUsedFirstRender) { // this insures that we only call this once per player load
return
@ -1186,6 +1236,7 @@ class CS3IPlayer : IPlayer {
}
}
@SuppressLint("UnsafeOptInUsageError")
private fun getSubSources(
onlineSourceFactory: HttpDataSource.Factory?,
offlineSourceFactory: DataSource.Factory?,
@ -1241,6 +1292,7 @@ class CS3IPlayer : IPlayer {
return exoPlayer != null
}
@SuppressLint("UnsafeOptInUsageError")
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
Log.i(TAG, "loadOnlinePlayer $link")
try {
@ -1257,18 +1309,37 @@ 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) {
link.playlist.map {
val mediaItems = when (link) {
is ExtractorLinkPlayList -> link.playlist.map {
MediaItemSlice(getMediaItem(mime, it.url), it.durationUs)
}
} else {
listOf(
is DrmExtractorLink -> {
listOf(
// Single sliced list with unset length
MediaItemSlice(
getMediaItem(mime, link.url), Long.MIN_VALUE,
drm = DrmMetadata(
kid = link.kid,
key = link.key,
uuid = link.uuid,
kty = link.kty,
keyRequestParameters = link.keyRequestParameters
)
)
)
}
else -> listOf(
// Single sliced list with unset length
MediaItemSlice(getMediaItem(mime, link.url), Long.MIN_VALUE)
)

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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())

View file

@ -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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -1,12 +1,204 @@
package com.lagradost.cloudstream3.utils
import android.net.Uri
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.extractors.AStreamHub
import com.lagradost.cloudstream3.extractors.Acefile
import com.lagradost.cloudstream3.extractors.Ahvsh
import com.lagradost.cloudstream3.extractors.Aico
import com.lagradost.cloudstream3.extractors.AsianLoad
import com.lagradost.cloudstream3.extractors.Bestx
import com.lagradost.cloudstream3.extractors.Blogger
import com.lagradost.cloudstream3.extractors.BullStream
import com.lagradost.cloudstream3.extractors.ByteShare
import com.lagradost.cloudstream3.extractors.Cda
import com.lagradost.cloudstream3.extractors.Cdnplayer
import com.lagradost.cloudstream3.extractors.Chillx
import com.lagradost.cloudstream3.extractors.CineGrabber
import com.lagradost.cloudstream3.extractors.Cinestart
import com.lagradost.cloudstream3.extractors.DBfilm
import com.lagradost.cloudstream3.extractors.Dailymotion
import com.lagradost.cloudstream3.extractors.DatabaseGdrive
import com.lagradost.cloudstream3.extractors.DatabaseGdrive2
import com.lagradost.cloudstream3.extractors.DesuArcg
import com.lagradost.cloudstream3.extractors.DesuDrive
import com.lagradost.cloudstream3.extractors.DesuOdchan
import com.lagradost.cloudstream3.extractors.DesuOdvip
import com.lagradost.cloudstream3.extractors.Dokicloud
import com.lagradost.cloudstream3.extractors.DoodCxExtractor
import com.lagradost.cloudstream3.extractors.DoodLaExtractor
import com.lagradost.cloudstream3.extractors.DoodPmExtractor
import com.lagradost.cloudstream3.extractors.DoodShExtractor
import com.lagradost.cloudstream3.extractors.DoodSoExtractor
import com.lagradost.cloudstream3.extractors.DoodToExtractor
import com.lagradost.cloudstream3.extractors.DoodWatchExtractor
import com.lagradost.cloudstream3.extractors.DoodWfExtractor
import com.lagradost.cloudstream3.extractors.DoodWsExtractor
import com.lagradost.cloudstream3.extractors.DoodYtExtractor
import com.lagradost.cloudstream3.extractors.Dooood
import com.lagradost.cloudstream3.extractors.Embedgram
import com.lagradost.cloudstream3.extractors.Evoload
import com.lagradost.cloudstream3.extractors.Evoload1
import com.lagradost.cloudstream3.extractors.FEmbed
import com.lagradost.cloudstream3.extractors.FEnet
import com.lagradost.cloudstream3.extractors.Fastream
import com.lagradost.cloudstream3.extractors.FeHD
import com.lagradost.cloudstream3.extractors.Fembed9hd
import com.lagradost.cloudstream3.extractors.FileMoon
import com.lagradost.cloudstream3.extractors.FileMoonIn
import com.lagradost.cloudstream3.extractors.FileMoonSx
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.Fplayer
import com.lagradost.cloudstream3.extractors.GMPlayer
import com.lagradost.cloudstream3.extractors.Gdriveplayer
import com.lagradost.cloudstream3.extractors.Gdriveplayerapi
import com.lagradost.cloudstream3.extractors.Gdriveplayerapp
import com.lagradost.cloudstream3.extractors.Gdriveplayerbiz
import com.lagradost.cloudstream3.extractors.Gdriveplayerco
import com.lagradost.cloudstream3.extractors.Gdriveplayerfun
import com.lagradost.cloudstream3.extractors.Gdriveplayerio
import com.lagradost.cloudstream3.extractors.Gdriveplayerme
import com.lagradost.cloudstream3.extractors.Gdriveplayerorg
import com.lagradost.cloudstream3.extractors.Gdriveplayerus
import com.lagradost.cloudstream3.extractors.Gofile
import com.lagradost.cloudstream3.extractors.GuardareStream
import com.lagradost.cloudstream3.extractors.Guccihide
import com.lagradost.cloudstream3.extractors.Hxfile
import com.lagradost.cloudstream3.extractors.JWPlayer
import com.lagradost.cloudstream3.extractors.Jawcloud
import com.lagradost.cloudstream3.extractors.Jeniusplay
import com.lagradost.cloudstream3.extractors.Keephealth
import com.lagradost.cloudstream3.extractors.KotakAnimeid
import com.lagradost.cloudstream3.extractors.Kotakajair
import com.lagradost.cloudstream3.extractors.Krakenfiles
import com.lagradost.cloudstream3.extractors.LayarKaca
import com.lagradost.cloudstream3.extractors.Linkbox
import com.lagradost.cloudstream3.extractors.Luxubu
import com.lagradost.cloudstream3.extractors.Lvturbo
import com.lagradost.cloudstream3.extractors.Maxstream
import com.lagradost.cloudstream3.extractors.Mcloud
import com.lagradost.cloudstream3.extractors.Megacloud
import com.lagradost.cloudstream3.extractors.Meownime
import com.lagradost.cloudstream3.extractors.MixDrop
import com.lagradost.cloudstream3.extractors.MixDropBz
import com.lagradost.cloudstream3.extractors.MixDropCh
import com.lagradost.cloudstream3.extractors.MixDropTo
import com.lagradost.cloudstream3.extractors.Movhide
import com.lagradost.cloudstream3.extractors.Moviehab
import com.lagradost.cloudstream3.extractors.MoviehabNet
import com.lagradost.cloudstream3.extractors.Moviesapi
import com.lagradost.cloudstream3.extractors.Moviesm4u
import com.lagradost.cloudstream3.extractors.Mp4Upload
import com.lagradost.cloudstream3.extractors.Mvidoo
import com.lagradost.cloudstream3.extractors.MwvnVizcloudInfo
import com.lagradost.cloudstream3.extractors.Neonime7n
import com.lagradost.cloudstream3.extractors.Neonime8n
import com.lagradost.cloudstream3.extractors.OkRu
import com.lagradost.cloudstream3.extractors.OkRuHttps
import com.lagradost.cloudstream3.extractors.Okrulink
import com.lagradost.cloudstream3.extractors.Pixeldrain
import com.lagradost.cloudstream3.extractors.PlayLtXyz
import com.lagradost.cloudstream3.extractors.PlayerVoxzer
import com.lagradost.cloudstream3.extractors.Rabbitstream
import com.lagradost.cloudstream3.extractors.Rasacintaku
import com.lagradost.cloudstream3.extractors.SBfull
import com.lagradost.cloudstream3.extractors.Sbasian
import com.lagradost.cloudstream3.extractors.Sbface
import com.lagradost.cloudstream3.extractors.Sbflix
import com.lagradost.cloudstream3.extractors.Sblona
import com.lagradost.cloudstream3.extractors.Sblongvu
import com.lagradost.cloudstream3.extractors.Sbnet
import com.lagradost.cloudstream3.extractors.Sbrapid
import com.lagradost.cloudstream3.extractors.Sbsonic
import com.lagradost.cloudstream3.extractors.Sbspeed
import com.lagradost.cloudstream3.extractors.Sbthe
import com.lagradost.cloudstream3.extractors.Sendvid
import com.lagradost.cloudstream3.extractors.ShaveTape
import com.lagradost.cloudstream3.extractors.Solidfiles
import com.lagradost.cloudstream3.extractors.SpeedoStream
import com.lagradost.cloudstream3.extractors.SpeedoStream1
import com.lagradost.cloudstream3.extractors.SpeedoStream2
import com.lagradost.cloudstream3.extractors.Ssbstream
import com.lagradost.cloudstream3.extractors.StreamM4u
import com.lagradost.cloudstream3.extractors.StreamSB
import com.lagradost.cloudstream3.extractors.StreamSB1
import com.lagradost.cloudstream3.extractors.StreamSB10
import com.lagradost.cloudstream3.extractors.StreamSB11
import com.lagradost.cloudstream3.extractors.StreamSB2
import com.lagradost.cloudstream3.extractors.StreamSB3
import com.lagradost.cloudstream3.extractors.StreamSB4
import com.lagradost.cloudstream3.extractors.StreamSB5
import com.lagradost.cloudstream3.extractors.StreamSB6
import com.lagradost.cloudstream3.extractors.StreamSB7
import com.lagradost.cloudstream3.extractors.StreamSB8
import com.lagradost.cloudstream3.extractors.StreamSB9
import com.lagradost.cloudstream3.extractors.StreamTape
import com.lagradost.cloudstream3.extractors.StreamTapeNet
import com.lagradost.cloudstream3.extractors.StreamhideCom
import com.lagradost.cloudstream3.extractors.StreamhideTo
import com.lagradost.cloudstream3.extractors.Streamhub2
import com.lagradost.cloudstream3.extractors.Streamlare
import com.lagradost.cloudstream3.extractors.StreamoUpload
import com.lagradost.cloudstream3.extractors.Streamplay
import com.lagradost.cloudstream3.extractors.Streamsss
import com.lagradost.cloudstream3.extractors.Supervideo
import com.lagradost.cloudstream3.extractors.Tantifilm
import com.lagradost.cloudstream3.extractors.Tomatomatela
import com.lagradost.cloudstream3.extractors.TomatomatelalClub
import com.lagradost.cloudstream3.extractors.Tubeless
import com.lagradost.cloudstream3.extractors.Upstream
import com.lagradost.cloudstream3.extractors.UpstreamExtractor
import com.lagradost.cloudstream3.extractors.Uqload
import com.lagradost.cloudstream3.extractors.Uqload1
import com.lagradost.cloudstream3.extractors.Uqload2
import com.lagradost.cloudstream3.extractors.Userload
import com.lagradost.cloudstream3.extractors.Userscloud
import com.lagradost.cloudstream3.extractors.Uservideo
import com.lagradost.cloudstream3.extractors.Vanfem
import com.lagradost.cloudstream3.extractors.Vicloud
import com.lagradost.cloudstream3.extractors.VidSrcExtractor
import com.lagradost.cloudstream3.extractors.VidSrcExtractor2
import com.lagradost.cloudstream3.extractors.VideoVard
import com.lagradost.cloudstream3.extractors.VideovardSX
import com.lagradost.cloudstream3.extractors.Vidgomunime
import com.lagradost.cloudstream3.extractors.Vidgomunimesb
import com.lagradost.cloudstream3.extractors.Vidmoly
import com.lagradost.cloudstream3.extractors.Vidmolyme
import com.lagradost.cloudstream3.extractors.Vido
import com.lagradost.cloudstream3.extractors.Vidstreamz
import com.lagradost.cloudstream3.extractors.Vizcloud
import com.lagradost.cloudstream3.extractors.Vizcloud2
import com.lagradost.cloudstream3.extractors.VizcloudCloud
import com.lagradost.cloudstream3.extractors.VizcloudDigital
import com.lagradost.cloudstream3.extractors.VizcloudInfo
import com.lagradost.cloudstream3.extractors.VizcloudLive
import com.lagradost.cloudstream3.extractors.VizcloudOnline
import com.lagradost.cloudstream3.extractors.VizcloudSite
import com.lagradost.cloudstream3.extractors.VizcloudXyz
import com.lagradost.cloudstream3.extractors.Voe
import com.lagradost.cloudstream3.extractors.Watchx
import com.lagradost.cloudstream3.extractors.WcoStream
import com.lagradost.cloudstream3.extractors.Wibufile
import com.lagradost.cloudstream3.extractors.XStreamCdn
import com.lagradost.cloudstream3.extractors.YourUpload
import com.lagradost.cloudstream3.extractors.YoutubeExtractor
import com.lagradost.cloudstream3.extractors.YoutubeMobileExtractor
import com.lagradost.cloudstream3.extractors.YoutubeNoCookieExtractor
import com.lagradost.cloudstream3.extractors.YoutubeShortLinkExtractor
import com.lagradost.cloudstream3.extractors.Yufiles
import com.lagradost.cloudstream3.extractors.Zorofile
import com.lagradost.cloudstream3.extractors.Zplayer
import com.lagradost.cloudstream3.extractors.ZplayerV2
import com.lagradost.cloudstream3.extractors.Ztreamhub
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 kotlin.collections.MutableList
import java.net.URL
import java.util.UUID
/**
* For use in the ConcatenatingMediaSource.
@ -35,22 +227,145 @@ 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
/**
* UUID for the ClearKey DRM scheme.
*
*
* ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up.
*/
val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL)
/**
* UUID for the Widevine DRM scheme.
*
*
* Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
*/
val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
/**
* UUID for the PlayReady DRM scheme.
*
*
* PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not
* provide PlayReady support.
*/
val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL)
open class DrmExtractorLink private constructor(
override val source: String,
override val name: String,
override val url: String,
override val referer: String,
override val quality: Int,
override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */
override val extractorData: String? = null,
override val type: ExtractorLinkType,
open val kid : String,
open val key : String,
open val uuid : UUID,
open val kty : String,
open val keyRequestParameters : HashMap<String, String>
) : ExtractorLink(
source, name, url, referer, quality, type, headers, extractorData
) {
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,
kid : String,
key : String,
uuid : UUID = CLEARKEY_UUID,
kty : String = "oct",
keyRequestParameters : HashMap<String, String> = hashMapOf(),
) : this(
source = source,
name = name,
url = url,
referer = referer,
quality = quality,
headers = headers,
extractorData = extractorData,
type = type ?: inferTypeFromUrl(url),
kid = kid,
key = key,
uuid = uuid,
keyRequestParameters = keyRequestParameters,
kty = kty,
)
}
open class ExtractorLink constructor(
open val source: String,
@ -58,12 +373,33 @@ open class ExtractorLink constructor(
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 +416,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 +493,7 @@ enum class Qualities(var value: Int, val defaultPriority: Int) {
else -> "${qual}p"
}
}
fun getStringByIntFull(quality: Int): String {
return when (quality) {
0 -> "Auto"

View file

@ -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