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 compileSdk = 33
buildToolsVersion = "30.0.3" buildToolsVersion = "34.0.0"
defaultConfig { defaultConfig {
applicationId = "com.lagradost.cloudstream3" applicationId = "com.lagradost.cloudstream3"
minSdk = 21 minSdk = 21
targetSdk = 29 targetSdk = 33
versionCode = 59 versionCode = 59
versionName = "4.1.8" versionName = "4.1.8"

View file

@ -5,6 +5,7 @@ import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.loadExtractor
@ -66,7 +67,7 @@ open class Pelisplus(val mainUrl: String) {
href, href,
page.url, page.url,
getQualityFromName(qual), 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.app
import com.lagradost.cloudstream3.argamap import com.lagradost.cloudstream3.argamap
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.loadExtractor
@ -70,7 +71,7 @@ class Vidstream(val mainUrl: String) {
href, href,
page.url, page.url,
getQualityFromName(qual), 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.app
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
class Vidstreamz : WcoStream() { 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") 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 { 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.app
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.INFER_TYPE
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI
open class Wibufile : ExtractorApi() { open class Wibufile : ExtractorApi() {
override val name: String = "Wibufile" override val name: String = "Wibufile"
@ -28,10 +28,8 @@ open class Wibufile : ExtractorApi() {
video ?: return, video ?: return,
"$mainUrl/", "$mainUrl/",
Qualities.Unknown.value, Qualities.Unknown.value,
URI(url).path.endsWith(".m3u8") type = INFER_TYPE
) )
) )
} }
} }

View file

@ -1,16 +1,24 @@
package com.lagradost.cloudstream3.ui package com.lagradost.cloudstream3.ui
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS 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.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.Resource
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -174,7 +182,7 @@ class APIRepository(val api: MainAPI) {
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit,
): Boolean { ): Boolean {
if (isInvalidData(data)) return false // this makes providers cleaner if (isInvalidData(data)) return false // this makes providers cleaner
return try { return try {

View file

@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.sortSubs import com.lagradost.cloudstream3.sortSubs
import com.lagradost.cloudstream3.sortUrls 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.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.ResultEpisode 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 generator = RepoLinkGenerator(listOf(epData))
val isSuccessful = safeApiCall { val isSuccessful = safeApiCall {
generator.generateLinks(clearCache = false, isCasting = true, generator.generateLinks(
clearCache = false, type = LoadType.Chromecast,
callback = { callback = {
it.first?.let { link -> it.first?.let { link ->
currentLinks.add(link) currentLinks.add(link)

View file

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

View file

@ -56,10 +56,10 @@ class DownloadFileGenerator(
override suspend fun generateLinks( override suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, type: LoadType,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit, subtitleCallback: (SubtitleData) -> Unit,
offset: Int, offset: Int
): Boolean { ): Boolean {
val meta = episodes[currentIndex + offset] val meta = episodes[currentIndex + offset]
callback(null to meta) callback(null to meta)

View file

@ -37,15 +37,18 @@ class ExtractorLinkGenerator(
override suspend fun generateLinks( override suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, type: LoadType,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit, subtitleCallback: (SubtitleData) -> Unit,
offset: Int offset: Int
): Boolean { ): Boolean {
subtitles.forEach(subtitleCallback) subtitles.forEach(subtitleCallback)
val allowedTypes = type.toSet()
links.forEach { links.forEach {
if(allowedTypes.contains(it.type)) {
callback.invoke(it to null) callback.invoke(it to null)
} }
}
return true return true
} }

View file

@ -1,8 +1,43 @@
package com.lagradost.cloudstream3.ui.player package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorLinkType
import com.lagradost.cloudstream3.utils.ExtractorUri 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 { interface IGenerator {
val hasCache: Boolean val hasCache: Boolean
@ -13,15 +48,15 @@ interface IGenerator {
fun goto(index: Int) fun goto(index: Int)
fun getCurrentId(): Int? // this is used to save data or read data about this id 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 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 getAll(): List<Any>? // this us used to get the metadata about all entries, not needed
/* not safe, must use try catch */ /* not safe, must use try catch */
suspend fun generateLinks( suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, type: LoadType,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit, subtitleCallback: (SubtitleData) -> Unit,
offset : Int = 0, offset: Int = 0,
): Boolean ): Boolean
} }

View file

@ -48,7 +48,7 @@ class LinkGenerator(
override suspend fun generateLinks( override suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, type: LoadType,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit, subtitleCallback: (SubtitleData) -> Unit,
offset: Int offset: Int

View file

@ -78,10 +78,10 @@ class PlayerGeneratorViewModel : ViewModel() {
if (generator?.hasCache == true && generator?.hasNext() == true) { if (generator?.hasCache == true && generator?.hasNext() == true) {
safeApiCall { safeApiCall {
generator?.generateLinks( generator?.generateLinks(
type = LoadType.InApp,
clearCache = false, clearCache = false,
isCasting = false, callback = {},
{}, subtitleCallback = {},
{},
offset = 1 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") Log.i(TAG, "loadLinks")
currentJob?.cancel() currentJob?.cancel()
@ -162,14 +162,14 @@ class PlayerGeneratorViewModel : ViewModel() {
// load more data // load more data
_loadingLinks.postValue(Resource.Loading()) _loadingLinks.postValue(Resource.Loading())
val loadingState = safeApiCall { val loadingState = safeApiCall {
generator?.generateLinks(clearCache = clearCache, isCasting = isCasting, { generator?.generateLinks(type = type,clearCache = clearCache, callback = {
currentLinks.add(it) currentLinks.add(it)
// Clone to prevent ConcurrentModificationException // Clone to prevent ConcurrentModificationException
normalSafeApiCall { normalSafeApiCall {
// Extra normalSafeApiCall since .toSet() iterates. // Extra normalSafeApiCall since .toSet() iterates.
_currentLinks.postValue(currentLinks.toSet()) _currentLinks.postValue(currentLinks.toSet())
} }
}, { }, subtitleCallback = {
currentSubs.add(it) currentSubs.add(it)
normalSafeApiCall { normalSafeApiCall {
_currentSubs.postValue(currentSubs.toSet()) _currentSubs.postValue(currentSubs.toSet())

View file

@ -67,18 +67,19 @@ class RepoLinkGenerator(
override suspend fun generateLinks( override suspend fun generateLinks(
clearCache: Boolean, clearCache: Boolean,
isCasting: Boolean, type: LoadType,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit, callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit, subtitleCallback: (SubtitleData) -> Unit,
offset: Int, offset: Int
): Boolean { ): Boolean {
val allowedTypes = type.toSet()
val index = currentIndex val index = currentIndex
val current = episodes.getOrNull(index + offset) ?: return false val current = episodes.getOrNull(index + offset) ?: return false
val (currentLinkCache, currentSubsCache) = if (clearCache) { val (currentLinkCache, currentSubsCache) = if (clearCache) {
Pair(mutableSetOf(), mutableSetOf()) Pair(mutableSetOf(), mutableSetOf())
} else { } 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() //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 currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
val currentSubsNames = mutableSetOf<String>() // makes all subs names 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) currentLinks.add(link.url)
callback(Pair(link, null)) callback(link to null)
} }
currentSubsCache.forEach { sub -> currentSubsCache.forEach { sub ->
@ -108,8 +109,8 @@ class RepoLinkGenerator(
val result = APIRepository( val result = APIRepository(
getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist") getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist")
).loadLinks(current.data, ).loadLinks(current.data,
isCasting, isCasting = LoadType.Chromecast == type,
{ file -> subtitleCallback = { file ->
val correctFile = PlayerSubtitleHelper.getSubtitleData(file) val correctFile = PlayerSubtitleHelper.getSubtitleData(file)
if (!currentSubsUrls.contains(correctFile.url)) { if (!currentSubsUrls.contains(correctFile.url)) {
currentSubsUrls.add(correctFile.url) currentSubsUrls.add(correctFile.url)
@ -132,12 +133,14 @@ class RepoLinkGenerator(
} }
} }
}, },
{ link -> callback = { link ->
Log.d(TAG, "Loaded ExtractorLink: $link") Log.d(TAG, "Loaded ExtractorLink: $link")
if (!currentLinks.contains(link.url)) { if (!currentLinks.contains(link.url)) {
if (!currentLinkCache.contains(link)) { if (!currentLinkCache.contains(link)) {
currentLinks.add(link.url) currentLinks.add(link.url)
if (allowedTypes.contains(link.type)) {
callback(Pair(link, null)) callback(Pair(link, null))
}
currentLinkCache.add(link) currentLinkCache.add(link)
//linkCache[index] = currentLinkCache //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.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.IGenerator 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.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction
@ -745,7 +746,7 @@ class ResultViewModel2 : ViewModel() {
val generator = RepoLinkGenerator(listOf(episode)) val generator = RepoLinkGenerator(listOf(episode))
val currentLinks = mutableSetOf<ExtractorLink>() val currentLinks = mutableSetOf<ExtractorLink>()
val currentSubs = mutableSetOf<SubtitleData>() val currentSubs = mutableSetOf<SubtitleData>()
generator.generateLinks(clearCache = false, isCasting = false, callback = { generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = {
it.first?.let { link -> it.first?.let { link ->
currentLinks.add(link) currentLinks.add(link)
} }
@ -825,7 +826,7 @@ class ResultViewModel2 : ViewModel() {
isVisible: Boolean = true isVisible: Boolean = true
) { ) {
if (activity == null) return 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) startChromecast(activity, result, data.links, data.subs, 0)
} }
} }
@ -936,7 +937,7 @@ class ResultViewModel2 : ViewModel() {
private fun loadLinks( private fun loadLinks(
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean, isVisible: Boolean,
isCasting: Boolean, type: LoadType,
clearCache: Boolean = false, clearCache: Boolean = false,
work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit)
) { ) {
@ -945,7 +946,7 @@ class ResultViewModel2 : ViewModel() {
val links = loadLinks( val links = loadLinks(
result, result,
isVisible = isVisible, isVisible = isVisible,
isCasting = isCasting, type = type,
clearCache = clearCache clearCache = clearCache
) )
if (!this.isActive) return@ioSafe if (!this.isActive) return@ioSafe
@ -956,11 +957,11 @@ class ResultViewModel2 : ViewModel() {
private var currentLoadLinkJob: Job? = null private var currentLoadLinkJob: Job? = null
private fun acquireSingleLink( private fun acquireSingleLink(
result: ResultEpisode, result: ResultEpisode,
isCasting: Boolean, type: LoadType,
text: UiText, text: UiText,
callback: (Pair<LinkLoadingResult, Int>) -> Unit, callback: (Pair<LinkLoadingResult, Int>) -> Unit,
) { ) {
loadLinks(result, isVisible = true, isCasting = isCasting) { links -> loadLinks(result, isVisible = true, type) { links ->
postPopup( postPopup(
text, text,
links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) { links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") }) {
@ -971,11 +972,10 @@ class ResultViewModel2 : ViewModel() {
private fun acquireSingleSubtitle( private fun acquireSingleSubtitle(
result: ResultEpisode, result: ResultEpisode,
isCasting: Boolean,
text: UiText, text: UiText,
callback: (Pair<LinkLoadingResult, Int>) -> Unit, callback: (Pair<LinkLoadingResult, Int>) -> Unit,
) { ) {
loadLinks(result, isVisible = true, isCasting = isCasting) { links -> loadLinks(result, isVisible = true, type = LoadType.Unknown) { links ->
postPopup( postPopup(
text, text,
links.subs.map { txt(it.name) }) links.subs.map { txt(it.name) })
@ -988,7 +988,7 @@ class ResultViewModel2 : ViewModel() {
private suspend fun CoroutineScope.loadLinks( private suspend fun CoroutineScope.loadLinks(
result: ResultEpisode, result: ResultEpisode,
isVisible: Boolean, isVisible: Boolean,
isCasting: Boolean, type: LoadType,
clearCache: Boolean = false, clearCache: Boolean = false,
): LinkLoadingResult { ): LinkLoadingResult {
val tempGenerator = RepoLinkGenerator(listOf(result)) val tempGenerator = RepoLinkGenerator(listOf(result))
@ -1002,7 +1002,7 @@ class ResultViewModel2 : ViewModel() {
} }
try { try {
updatePage() updatePage()
tempGenerator.generateLinks(clearCache, isCasting, { (link, _) -> tempGenerator.generateLinks(clearCache, type, { (link, _) ->
if (link != null) { if (link != null) {
links += link links += link
updatePage() updatePage()
@ -1272,7 +1272,6 @@ class ResultViewModel2 : ViewModel() {
acquireSingleSubtitle( acquireSingleSubtitle(
click.data, click.data,
false,
txt(R.string.episode_action_download_subtitle) txt(R.string.episode_action_download_subtitle)
) { (links, index) -> ) { (links, index) ->
downloadSubtitle( downloadSubtitle(
@ -1317,7 +1316,7 @@ class ResultViewModel2 : ViewModel() {
val response = currentResponse ?: return val response = currentResponse ?: return
acquireSingleLink( acquireSingleLink(
click.data, click.data,
false, LoadType.InAppDownload,
txt(R.string.episode_action_download_mirror) txt(R.string.episode_action_download_mirror)
) { (result, index) -> ) { (result, index) ->
ioSafe { ioSafe {
@ -1347,7 +1346,7 @@ class ResultViewModel2 : ViewModel() {
loadLinks( loadLinks(
click.data, click.data,
isVisible = false, isVisible = false,
isCasting = false, type = LoadType.InApp,
clearCache = true clearCache = true
) )
} }
@ -1356,7 +1355,7 @@ class ResultViewModel2 : ViewModel() {
ACTION_CHROME_CAST_MIRROR -> { ACTION_CHROME_CAST_MIRROR -> {
acquireSingleLink( acquireSingleLink(
click.data, click.data,
isCasting = true, LoadType.Chromecast,
txt(R.string.episode_action_chromecast_mirror) txt(R.string.episode_action_chromecast_mirror)
) { (result, index) -> ) { (result, index) ->
startChromecast(activity, click.data, result.links, result.subs, index) startChromecast(activity, click.data, result.links, result.subs, index)
@ -1365,7 +1364,7 @@ class ResultViewModel2 : ViewModel() {
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink( ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
click.data, click.data,
isCasting = true, LoadType.Browser,
txt(R.string.episode_action_play_in_browser) txt(R.string.episode_action_play_in_browser)
) { (result, index) -> ) { (result, index) ->
try { try {
@ -1380,7 +1379,7 @@ class ResultViewModel2 : ViewModel() {
ACTION_COPY_LINK -> { ACTION_COPY_LINK -> {
acquireSingleLink( acquireSingleLink(
click.data, click.data,
isCasting = true, LoadType.ExternalApp,
txt(R.string.episode_action_copy_link) txt(R.string.episode_action_copy_link)
) { (result, index) -> ) { (result, index) ->
val act = activity ?: return@acquireSingleLink val act = activity ?: return@acquireSingleLink
@ -1399,7 +1398,7 @@ class ResultViewModel2 : ViewModel() {
} }
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { 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()) { if (links.links.isEmpty()) {
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
return@loadLinks return@loadLinks
@ -1415,7 +1414,7 @@ class ResultViewModel2 : ViewModel() {
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink( ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
click.data, click.data,
isCasting = true, LoadType.Chromecast,
txt( txt(
R.string.episode_action_play_in_format, R.string.episode_action_play_in_format,
txt(R.string.player_settings_play_in_web) txt(R.string.player_settings_play_in_web)
@ -1432,7 +1431,7 @@ class ResultViewModel2 : ViewModel() {
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink( ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
click.data, click.data,
isCasting = true, LoadType.Chromecast,
txt( txt(
R.string.episode_action_play_in_format, R.string.episode_action_play_in_format,
txt(R.string.player_settings_play_in_mpv) txt(R.string.player_settings_play_in_mpv)
@ -1461,7 +1460,6 @@ class ResultViewModel2 : ViewModel() {
if (index >= 0) if (index >= 0)
it.goto(index) it.goto(index)
} }
} ?: return, list } ?: return, list
) )
) )
@ -2173,7 +2171,7 @@ class ResultViewModel2 : ViewModel() {
trailerData.extractorUrl, trailerData.extractorUrl,
trailerData.referer ?: "", trailerData.referer ?: "",
Qualities.Unknown.value, Qualities.Unknown.value,
trailerData.extractorUrl.contains(".m3u8") type = INFER_TYPE
) )
) to arrayListOf() ) to arrayListOf()
} else { } else {

View file

@ -55,7 +55,11 @@ object CastHelper {
val builder = MediaInfo.Builder(link.url) val builder = MediaInfo.Builder(link.url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .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) .setMetadata(movieMetadata)
.setMediaTracks(tracks) .setMediaTracks(tracks)
data?.let { data?.let {

View file

@ -1,12 +1,204 @@
package com.lagradost.cloudstream3.utils package com.lagradost.cloudstream3.utils
import android.net.Uri 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.mvvm.logError
import com.lagradost.cloudstream3.extractors.* import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.jsoup.Jsoup import org.jsoup.Jsoup
import kotlin.collections.MutableList import java.net.URL
import java.util.UUID
/** /**
* For use in the ConcatenatingMediaSource. * For use in the ConcatenatingMediaSource.
@ -35,22 +227,145 @@ data class ExtractorLinkPlayList(
val playlist: List<PlayListItem>, val playlist: List<PlayListItem>,
override val referer: String, override val referer: String,
override val quality: Int, override val quality: Int,
override val isM3u8: Boolean = false, val isM3u8: Boolean = false,
override val headers: Map<String, String> = mapOf(), override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */ /** Used for getExtractorVerifierJob() */
override val extractorData: String? = null, override val extractorData: String? = null,
override val type: ExtractorLinkType,
) : ExtractorLink( ) : ExtractorLink(
source, source = source,
name, name = name,
// Blank as un-used url = "",
"", referer = referer,
referer, quality = quality,
quality, headers = headers,
isM3u8, extractorData = extractorData,
headers, type = type
extractorData ) {
) 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 class ExtractorLink constructor(
open val source: String, open val source: String,
@ -58,12 +373,33 @@ open class ExtractorLink constructor(
override val url: String, override val url: String,
override val referer: String, override val referer: String,
open val quality: Int, open val quality: Int,
open val isM3u8: Boolean = false,
override val headers: Map<String, String> = mapOf(), override val headers: Map<String, String> = mapOf(),
/** Used for getExtractorVerifierJob() */ /** Used for getExtractorVerifierJob() */
open val extractorData: String? = null, open val extractorData: String? = null,
open val isDash: Boolean = false, open val type: ExtractorLinkType,
) : VideoDownloadManager.IDownloadableMinimum { ) : 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. * Old constructor without isDash, allows for backwards compatibility with extensions.
* Should be removed after all extensions have updated their cloudstream.jar * Should be removed after all extensions have updated their cloudstream.jar
@ -80,8 +416,30 @@ open class ExtractorLink constructor(
extractorData: String? = null extractorData: String? = null
) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false) ) : 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 { 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" else -> "${qual}p"
} }
} }
fun getStringByIntFull(quality: Int): String { fun getStringByIntFull(quality: Int): String {
return when (quality) { return when (quality) {
0 -> "Auto" 0 -> "Auto"

View file

@ -53,7 +53,7 @@ import java.io.Closeable
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.net.URL import java.lang.IllegalArgumentException
import java.util.* import java.util.*
const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" 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 */ /** how many bytes every connection should be, by default it is 10 MiB */
chuckSize: Long = (1 shl 20) * 10, chuckSize: Long = (1 shl 20) * 10,
/** maximum bytes in the buffer that responds */ /** 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 { ): LazyStreamDownloadData {
// we don't want to make a separate connection for every 1kb // we don't want to make a separate connection for every 1kb
require(chuckSize > 1000) require(chuckSize > 1000)
@ -963,7 +966,7 @@ object VideoDownloadManager {
var downloadLength: Long? = null var downloadLength: Long? = null
var totalLength: 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 // is the equivalent of [startByte..EOF] as we don't know the size we can only do one
// connection // connection
LongArray(1) { startByte } LongArray(1) { startByte }
@ -1024,6 +1027,7 @@ object VideoDownloadManager {
} }
} }
/** download a file that consist of a single stream of data*/
suspend fun downloadThing( suspend fun downloadThing(
context: Context, context: Context,
link: IDownloadableMinimum, link: IDownloadableMinimum,
@ -1035,8 +1039,7 @@ object VideoDownloadManager {
createNotificationCallback: (CreateNotificationMetadata) -> Unit, createNotificationCallback: (CreateNotificationMetadata) -> Unit,
parallelConnections: Int = 3 parallelConnections: Int = 3
): DownloadStatus = withContext(Dispatchers.IO) { ): DownloadStatus = withContext(Dispatchers.IO) {
// we cant download torrents with this implementation, aria2c might be used in the future if (parallelConnections < 1) {
if (link.url.startsWith("magnet") || link.url.endsWith(".torrent") || parallelConnections < 1) {
return@withContext DOWNLOAD_INVALID_INPUT return@withContext DOWNLOAD_INVALID_INPUT
} }
@ -1529,6 +1532,11 @@ object VideoDownloadManager {
notificationCallback: (Int, Notification) -> Unit, notificationCallback: (Int, Notification) -> Unit,
tryResume: Boolean = false, tryResume: Boolean = false,
): DownloadStatus { ): 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) val name = getFileName(context, ep)
// Make sure this is cancelled when download is done or cancelled. // Make sure this is cancelled when download is done or cancelled.
@ -1557,7 +1565,8 @@ object VideoDownloadManager {
} }
try { try {
if (link.isM3u8 || normalSafeApiCall { URL(link.url).path.endsWith(".m3u8") } == true) { when(link.type) {
ExtractorLinkType.M3U8 -> {
val startIndex = if (tryResume) { val startIndex = if (tryResume) {
context.getKey<DownloadedFileInfo>( context.getKey<DownloadedFileInfo>(
KEY_DOWNLOAD_INFO, KEY_DOWNLOAD_INFO,
@ -1575,7 +1584,8 @@ object VideoDownloadManager {
startIndex, startIndex,
callback, parallelConnections = maxConcurrentConnections callback, parallelConnections = maxConcurrentConnections
) )
} else { }
ExtractorLinkType.VIDEO -> {
return downloadThing( return downloadThing(
context, context,
link, link,
@ -1587,6 +1597,8 @@ object VideoDownloadManager {
callback, parallelConnections = maxConcurrentConnections callback, parallelConnections = maxConcurrentConnections
) )
} }
else -> throw IllegalArgumentException("unsuported download type")
}
} catch (t: Throwable) { } catch (t: Throwable) {
return DOWNLOAD_FAILED return DOWNLOAD_FAILED
} finally { } finally {