chromecast stuff

This commit is contained in:
LagradOst 2021-06-14 18:58:43 +02:00
parent 6579eb3083
commit 50abf2c74c
14 changed files with 323 additions and 118 deletions

View file

@ -53,7 +53,7 @@ abstract class MainAPI {
} }
// callback is fired once a link is found, will return true if method is executed successfully // callback is fired once a link is found, will return true if method is executed successfully
open fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean { open fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
return false return false
} }
} }
@ -169,8 +169,8 @@ data class AnimeLoadResponse(
override val posterUrl: String?, override val posterUrl: String?,
override val year: Int?, override val year: Int?,
val dubEpisodes: ArrayList<Any>?, val dubEpisodes: ArrayList<String>?,
val subEpisodes: ArrayList<Any>?, val subEpisodes: ArrayList<String>?,
val showStatus: ShowStatus?, val showStatus: ShowStatus?,
override val plot: String?, override val plot: String?,

View file

@ -2,32 +2,27 @@ package com.lagradost.cloudstream3
import android.app.PictureInPictureParams import android.app.PictureInPictureParams
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.google.android.exoplayer2.ext.cast.CastPlayer import com.google.android.gms.cast.ApplicationMetadata
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.gms.cast.Cast
import com.google.android.gms.cast.MediaInfo import com.google.android.gms.cast.LaunchOptions
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.SessionManagerListener
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.lagradost.cloudstream3.UIHelper.checkWrite import com.lagradost.cloudstream3.UIHelper.checkWrite
import com.lagradost.cloudstream3.UIHelper.hasPIPPermission import com.lagradost.cloudstream3.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.UIHelper.requestRW import com.lagradost.cloudstream3.UIHelper.requestRW
import com.lagradost.cloudstream3.UIHelper.shouldShowPIPMode import com.lagradost.cloudstream3.UIHelper.shouldShowPIPMode
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
/*, ViewModelStoreOwner { /*, ViewModelStoreOwner {
private val appViewModelStore: ViewModelStore by lazy { private val appViewModelStore: ViewModelStore by lazy {
@ -110,8 +105,8 @@ class MainActivity : AppCompatActivity() {
requestRW() requestRW()
if (checkWrite()) return if (checkWrite()) return
} }
CastButtonFactory.setUpMediaRouteButton(this, media_route_button) CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
/* /*
val castContext = CastContext.getSharedInstance(applicationContext) val castContext = CastContext.getSharedInstance(applicationContext)
fun buildMediaQueueItem(video: String): MediaQueueItem { fun buildMediaQueueItem(video: String): MediaQueueItem {

View file

@ -178,7 +178,7 @@ class ShiroProvider : MainAPI() {
val mapped = response.let { mapper.readValue<AnimePage>(it.text) } val mapped = response.let { mapper.readValue<AnimePage>(it.text) }
val data = mapped.data val data = mapped.data
val isDubbed = data.language == "dubbed" val isDubbed = data.language == "dubbed"
val episodes = ArrayList<Any>(data.episodes ?: ArrayList()) val episodes = ArrayList<String>(data.episodes?.map { it.videos[0].video_id } ?: ArrayList<String>())
val status = when (data.status) { val status = when (data.status) {
"current" -> ShowStatus.Ongoing "current" -> ShowStatus.Ongoing
"finished" -> ShowStatus.Completed "finished" -> ShowStatus.Completed
@ -205,12 +205,9 @@ class ShiroProvider : MainAPI() {
) )
} }
override fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean { override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
if (data is ShiroEpisodes) { return Vidstream().getUrl(data, isCasting) {
return Vidstream().getUrl(data.videos[0].video_id, isCasting) {
callback.invoke(it) callback.invoke(it)
} }
} }
return false
}
} }

View file

@ -9,19 +9,25 @@ import androidx.appcompat.app.AlertDialog
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastSession import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.uicontroller.UIController import com.google.android.gms.cast.framework.media.uicontroller.UIController
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.hideSystemUI import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.utils.Coroutines import com.lagradost.cloudstream3.mvvm.safeApiCall
import kotlinx.coroutines.delay import com.lagradost.cloudstream3.sortUrls
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject import org.json.JSONObject
import java.lang.Exception
class SkipOpController(val view: ImageView) : UIController() { class SkipOpController(val view: ImageView) : UIController() {
init { init {
@ -32,8 +38,14 @@ class SkipOpController(val view: ImageView) : UIController() {
} }
} }
data class MetadataSource(val name: String) data class MetadataHolder(
data class MetadataHolder(val data: List<MetadataSource>) val apiName: String,
val title: String?,
val poster: String?,
val currentEpisodeIndex: Int,
val episodes: List<ResultEpisode>,
val currentLinks: List<ExtractorLink>,
)
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() { class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule()) private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
@ -42,30 +54,48 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
init { init {
view.setImageResource(R.drawable.ic_baseline_playlist_play_24) view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
view.setOnClickListener { view.setOnClickListener {
//remoteMediaClient.mediaQueue.itemCount
//println(remoteMediaClient.mediaInfo.customData)
//remoteMediaClient.queueJumpToItem()
lateinit var dialog: AlertDialog lateinit var dialog: AlertDialog
val holder = getCurrentMetaData() val holder = getCurrentMetaData()
if (holder != null) { if (holder != null) {
val items = holder.data val items = holder.currentLinks
if (items.isNotEmpty() && remoteMediaClient.currentItem != null) { if (items.isNotEmpty() && remoteMediaClient.currentItem != null) {
val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom) val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
builder.setTitle("Pick source") builder.setTitle("Pick source")
//https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation
val contentUrl = (remoteMediaClient.currentItem.media.contentUrl
?: remoteMediaClient.currentItem.media.contentId)
builder.setSingleChoiceItems( builder.setSingleChoiceItems(
items.map { it.name }.toTypedArray(), items.map { it.name }.toTypedArray(),
remoteMediaClient.mediaQueue.indexOfItemWithId(remoteMediaClient.currentItem.itemId) items.indexOfFirst { it.url == contentUrl }
) { _, which -> ) { _, which ->
val itemId = remoteMediaClient.mediaQueue.itemIds?.get(which) val epData = holder.episodes[holder.currentEpisodeIndex]
itemId?.let { id -> val mediaItem = getMediaInfo(epData,
remoteMediaClient.queueJumpToItem( holder,
id, holder.currentLinks[which],
remoteMediaClient.approximateStreamPosition, remoteMediaClient.mediaInfo.customData)
remoteMediaClient.mediaInfo.customData
) val startAt = remoteMediaClient.approximateStreamPosition
try {
val currentIdIndex = getItemIndex() ?: return@setSingleChoiceItems
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex + 1)
if (nextId != null) {
remoteMediaClient.queueInsertAndPlayItem(MediaQueueItem.Builder(mediaItem).build(),
nextId,
startAt,
JSONObject())
} else {
remoteMediaClient.load(mediaItem, true, startAt)
}
} catch (e: Exception) {
remoteMediaClient.load(mediaItem, true, startAt)
} }
dialog.dismiss() dialog.dismiss()
@ -77,26 +107,78 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
} }
} }
private fun getItemIndex(): Int? {
val index = remoteMediaClient?.mediaQueue?.itemIds?.indexOf(remoteMediaClient.currentItem.itemId)
return if (index == null || index < 0) null else index
}
private fun getCurrentMetaData(): MetadataHolder? { private fun getCurrentMetaData(): MetadataHolder? {
return try { return try {
val data = remoteMediaClient.mediaInfo.customData val data = remoteMediaClient?.mediaInfo?.customData?.toString()
mapper.readValue<MetadataHolder>(data.toString()) data?.toKotlinObject()
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
} }
var isLoadingMore = false
override fun onMediaStatusUpdated() { override fun onMediaStatusUpdated() {
super.onMediaStatusUpdated() super.onMediaStatusUpdated()
view.visibility = val meta = getCurrentMetaData()
if ((getCurrentMetaData()?.data?.size
view.visibility = if ((meta?.currentLinks?.size
?: 0) > 1 ?: 0) > 1
) VISIBLE else INVISIBLE ) VISIBLE else INVISIBLE
try {
if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) {
val currentIdIndex = getItemIndex() ?: return
val itemCount = remoteMediaClient?.mediaQueue?.itemCount
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
isLoadingMore = true
main {
val index = meta.currentEpisodeIndex + 1
val epData = meta.episodes[index]
val links = ArrayList<ExtractorLink>()
val res = safeApiCall {
getApiFromName(meta.apiName).loadLinks(epData.data, true) {
for (i in links) {
if (i.url == it.url) return@loadLinks
}
links.add(it)
}
}
if (res is Resource.Success) {
val sorted = sortUrls(links)
if (sorted.isNotEmpty()) {
val jsonCopy = meta.copy(currentLinks = sorted, currentEpisodeIndex = index)
val done = withContext(Dispatchers.IO) {
getMediaInfo(epData,
meta,
sorted.first(),
JSONObject(mapper.writeValueAsString(jsonCopy)))
}
remoteMediaClient?.queueAppendItem(MediaQueueItem.Builder(done).build(), JSONObject())
isLoadingMore = false
}
}
}
}
}
} catch (e: Exception) {
println(e)
}
} }
override fun onSessionConnected(castSession: CastSession?) { override fun onSessionConnected(castSession: CastSession?) {
super.onSessionConnected(castSession) super.onSessionConnected(castSession)
remoteMediaClient.queueSetRepeatMode(REPEAT_MODE_REPEAT_SINGLE, JSONObject()) remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
} }
} }

View file

@ -74,9 +74,11 @@ import com.lagradost.cloudstream3.UIHelper.requestLocalAudioFocus
import com.lagradost.cloudstream3.UIHelper.showSystemUI import com.lagradost.cloudstream3.UIHelper.showSystemUI
import com.lagradost.cloudstream3.UIHelper.toPx import com.lagradost.cloudstream3.UIHelper.toPx
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeDirectly import com.lagradost.cloudstream3.mvvm.observeDirectly
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultViewModel import com.lagradost.cloudstream3.ui.result.ResultViewModel
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -142,6 +144,8 @@ class PlayerFragment : Fragment() {
private val mapper = JsonMapper.builder().addModule(KotlinModule()) private val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
lateinit var apiName: String
private var isFullscreen = false private var isFullscreen = false
private var isPlayerPlaying = true private var isPlayerPlaying = true
private lateinit var viewModel: ResultViewModel private lateinit var viewModel: ResultViewModel
@ -733,7 +737,17 @@ class PlayerFragment : Fragment() {
val epData = getEpisode() ?: return@addCastStateListener val epData = getEpisode() ?: return@addCastStateListener
val index = links.indexOf(getCurrentUrl()) val index = links.indexOf(getCurrentUrl())
context?.startCast(
apiName,
currentHeaderName,
currentPoster,
epData.index,
episodes,
links,
index,
exoPlayer.currentPosition)
/*
val customData = val customData =
links.map { JSONObject().put("name", it.name) } links.map { JSONObject().put("name", it.name) }
val jsonArray = JSONArray() val jsonArray = JSONArray()
@ -773,8 +787,9 @@ class PlayerFragment : Fragment() {
if (index > 0) index else 0, if (index > 0) index else 0,
exoPlayer.currentPosition, exoPlayer.currentPosition,
MediaStatus.REPEAT_MODE_REPEAT_SINGLE MediaStatus.REPEAT_MODE_REPEAT_SINGLE
) )*/
// activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false) // activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false)
releasePlayer()
activity?.popCurrentPage() activity?.popCurrentPage()
} }
} }
@ -820,6 +835,10 @@ class PlayerFragment : Fragment() {
} }
} }
observe(viewModel.apiName) {
apiName = it
}
overlay_loading_skip_button?.alpha = 0.5f overlay_loading_skip_button?.alpha = 0.5f
observeDirectly(viewModel.allEpisodes) { _allEpisodes -> observeDirectly(viewModel.allEpisodes) { _allEpisodes ->
allEpisodes = _allEpisodes allEpisodes = _allEpisodes

View file

@ -20,11 +20,13 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.RequestOptions.bitmapTransform import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.fasterxml.jackson.annotation.JsonProperty
import com.google.android.exoplayer2.ext.cast.CastPlayer import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.MediaInfo import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE
import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastContext
@ -38,6 +40,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.player.PlayerData import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result.*
@ -52,7 +55,7 @@ data class ResultEpisode(
val poster: String?, val poster: String?,
val episode: Int, val episode: Int,
val season: Int?, val season: Int?,
val data: Any, val data: String,
val apiName: String, val apiName: String,
val id: Int, val id: Int,
val index: Int, val index: Int,
@ -80,6 +83,7 @@ class ResultFragment : Fragment() {
private lateinit var viewModel: ResultViewModel private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap() private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
var currentHeaderName: String? = null var currentHeaderName: String? = null
var currentEpisodes: ArrayList<ResultEpisode>? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -142,28 +146,35 @@ class ResultFragment : Fragment() {
ArrayList(), ArrayList(),
result_episodes, result_episodes,
) { episodeClick -> ) { episodeClick ->
val id = episodeClick.data.id //val id = episodeClick.data.id
val index = episodeClick.data.index val index = episodeClick.data.index
val buildInPlayer = true val buildInPlayer = true
when (episodeClick.action) { when (episodeClick.action) {
ACTION_CHROME_CAST_EPISODE -> { ACTION_CHROME_CAST_EPISODE -> {
/*
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null); val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null);
builder.setView(customLayout) builder.setView(customLayout)
val dialog = builder.create()*/ val dialog = builder.create()
//dialog.show() dialog.show()
Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show() Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
viewModel.loadEpisode(episodeClick.data, false) { data -> viewModel.loadEpisode(episodeClick.data, true) { data ->
// dialog.dismiss() dialog.dismiss()
when (data) { when (data) {
is Resource.Failure -> { is Resource.Failure -> {
Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show() Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
} }
is Resource.Success -> { is Resource.Success -> {
val eps = currentEpisodes ?: return@loadEpisode
context?.startCast(
apiName ?: return@loadEpisode,
currentHeaderName,
currentPoster,
episodeClick.data.index,
eps,
sortUrls(data.value))
/*
val epData = episodeClick.data val epData = episodeClick.data
val links = sortUrls(data.value) val links = sortUrls(data.value)
@ -200,12 +211,14 @@ class ResultFragment : Fragment() {
}.toTypedArray() }.toTypedArray()
val castPlayer = CastPlayer(castContext) val castPlayer = CastPlayer(castContext)
castPlayer.loadItems( castPlayer.loadItems(
mediaItems, mediaItems,
0, 0,
epData.progress, epData.progress,
REPEAT_MODE_REPEAT_SINGLE REPEAT_MODE_REPEAT_SINGLE
) //REPEAT_MODE_REPEAT_SINGLE
)*/
} }
} }
} }
@ -244,6 +257,7 @@ class ResultFragment : Fragment() {
observe(viewModel.episodes) { episodes -> observe(viewModel.episodes) { episodes ->
if (result_episodes == null || result_episodes.adapter == null) return@observe if (result_episodes == null || result_episodes.adapter == null) return@observe
result_episodes_text.text = "${episodes.size} Episode${if (episodes.size == 1) "" else "s"}" result_episodes_text.text = "${episodes.size} Episode${if (episodes.size == 1) "" else "s"}"
currentEpisodes = episodes
(result_episodes.adapter as EpisodeAdapter).cardList = episodes (result_episodes.adapter as EpisodeAdapter).cardList = episodes
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged() (result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
} }

View file

@ -82,7 +82,8 @@ class ResultViewModel : ViewModel() {
d.apiName, d.apiName,
(d.url).hashCode(), (d.url).hashCode(),
0, 0,
0, 0))) 0, 0,
)))
} }
} }
} }
@ -99,6 +100,8 @@ class ResultViewModel : ViewModel() {
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes
private var _apiName: MutableLiveData<String> = MutableLiveData() private var _apiName: MutableLiveData<String> = MutableLiveData()
val apiName: LiveData<String> get() = _apiName
fun loadEpisode( fun loadEpisode(
episode: ResultEpisode, episode: ResultEpisode,
@ -110,7 +113,7 @@ class ResultViewModel : ViewModel() {
private fun loadEpisode( private fun loadEpisode(
id: Int, id: Int,
data: Any, data: String,
isCasting: Boolean, isCasting: Boolean,
callback: (Resource<ArrayList<ExtractorLink>>) -> Unit, callback: (Resource<ArrayList<ExtractorLink>>) -> Unit,
) = ) =
@ -124,7 +127,6 @@ class ResultViewModel : ViewModel() {
for (i in links) { for (i in links) {
if (i.url == it.url) return@loadLinks if (i.url == it.url) return@loadLinks
} }
println("LINK ADDED::::: " + it.url)
links.add(it) links.add(it)
_allEpisodes.value?.set(id, links) _allEpisodes.value?.set(id, links)

View file

@ -0,0 +1,95 @@
package com.lagradost.cloudstream3.utils
import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.google.android.exoplayer2.ext.cast.CastPlayer
import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.gms.cast.CastStatusCodes
import com.google.android.gms.cast.MediaInfo
import com.google.android.gms.cast.MediaMetadata
import com.google.android.gms.cast.MediaQueueItem
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_OFF
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.common.api.PendingResult
import com.google.android.gms.common.images.WebImage
import com.lagradost.cloudstream3.ui.MetadataHolder
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.main
import kotlinx.coroutines.awaitAll
import org.json.JSONObject
import kotlin.concurrent.thread
object CastHelper {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
fun getMediaInfo(epData: ResultEpisode, holder: MetadataHolder, link: ExtractorLink, data: JSONObject?): MediaInfo {
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title)
val srcPoster = epData.poster ?: holder.poster
if (srcPoster != null) {
movieMetadata.addImage(WebImage(Uri.parse(srcPoster)))
}
return MediaInfo.Builder(link.url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.VIDEO_UNKNOWN)
.setCustomData(data)
.setMetadata(movieMetadata)
.build()
}
fun awaitLinks(pending: PendingResult<RemoteMediaClient.MediaChannelResult>?) {
if (pending == null) return
thread {
val res = pending.await()
when (res.status?.statusCode) {
CastStatusCodes.FAILED -> {
println("FAILED WITH DATA: " + res.customData)
}
else -> {
}
}
}
}
fun Context.startCast(
apiName: String,
title: String?,
poster: String?,
currentEpisodeIndex: Int,
episodes: List<ResultEpisode>,
currentLinks: List<ExtractorLink>,
startIndex: Int? = null,
startTime: Long? = null,
) {
if (episodes.isEmpty()) return
val castContext = CastContext.getSharedInstance(this)
val epData = episodes[currentEpisodeIndex]
val holder = MetadataHolder(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks)
val mediaItem =
getMediaInfo(epData, holder, currentLinks[startIndex ?: 0], JSONObject(mapper.writeValueAsString(holder)))
val castPlayer = CastPlayer(castContext)
castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF
castPlayer.stop()
awaitLinks(castPlayer.loadItem(
MediaQueueItem.Builder(mediaItem).build(),
startTime ?: 0,
))
}
}

View file

@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.utils
import com.lagradost.cloudstream3.utils.extractors.MixDrop import com.lagradost.cloudstream3.utils.extractors.MixDrop
import com.lagradost.cloudstream3.utils.extractors.Mp4Upload import com.lagradost.cloudstream3.utils.extractors.Mp4Upload
import com.lagradost.cloudstream3.utils.extractors.Shiro import com.lagradost.cloudstream3.utils.extractors.Shiro
import com.lagradost.cloudstream3.cloudstream3.extractors.StreamTape import com.lagradost.cloudstream3.utils.extractors.StreamTape
import com.lagradost.cloudstream3.utils.extractors.XStreamCdn import com.lagradost.cloudstream3.utils.extractors.XStreamCdn
data class ExtractorLink( data class ExtractorLink(
@ -19,8 +19,6 @@ fun ExtractorLink.getId() : Int {
return url.hashCode() return url.hashCode()
} }
enum class Qualities(var value: Int) { enum class Qualities(var value: Int) {
Unknown(0), Unknown(0),
SD(-1), // 360p - 480p SD(-1), // 360p - 480p

View file

@ -6,7 +6,7 @@ class MixDrop : ExtractorApi() {
override val name: String = "MixDrop" override val name: String = "MixDrop"
override val mainUrl: String = "https://mixdrop.co" override val mainUrl: String = "https://mixdrop.co"
private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""") private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""")
override val requiresReferer = true override val requiresReferer = false
override fun getExtractorUrl(id: String): String { override fun getExtractorUrl(id: String): String {
return "$mainUrl/e/$id" return "$mainUrl/e/$id"

View file

@ -1,4 +1,4 @@
package com.lagradost.cloudstream3.cloudstream3.extractors package com.lagradost.cloudstream3.utils.extractors
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -7,9 +7,9 @@ import com.lagradost.cloudstream3.utils.Qualities
class StreamTape : ExtractorApi() { class StreamTape : ExtractorApi() {
override val name: String = "StreamTape" override val name: String = "StreamTape"
override val mainUrl: String = "https://streamtape.com" override val mainUrl: String = "https://streamtape.com"
override val requiresReferer = true override val requiresReferer = false
// Because they add concatenation to fuck up scrapers // Because they add concatenation to fuck up scrapers, DON'T LET LAG CODE ANYTHING
private val linkRegex = private val linkRegex =
Regex("""(i(|" \+ ')d(|" \+ ')=.*?&(|" \+ ')e(|" \+ ')x(|" \+ ')p(|" \+ ')i(|" \+ ')r(|" \+ ')e(|" \+ ')s(|" \+ ')=.*?&(|" \+ ')i(|" \+ ')p(|" \+ ')=.*?&(|" \+ ')t(|" \+ ')o(|" \+ ')k(|" \+ ')e(|" \+ ')n(|" \+ ')=.*)'""") Regex("""(i(|" \+ ')d(|" \+ ')=.*?&(|" \+ ')e(|" \+ ')x(|" \+ ')p(|" \+ ')i(|" \+ ')r(|" \+ ')e(|" \+ ')s(|" \+ ')=.*?&(|" \+ ')i(|" \+ ')p(|" \+ ')=.*?&(|" \+ ')t(|" \+ ')o(|" \+ ')k(|" \+ ')e(|" \+ ')n(|" \+ ')=.*)'""")

View file

@ -12,6 +12,7 @@ class Vidstream {
private fun getExtractorUrl(id: String): String { private fun getExtractorUrl(id: String): String {
return "$mainUrl/streaming.php?id=$id" return "$mainUrl/streaming.php?id=$id"
} }
private val normalApis = arrayListOf(Shiro(), MultiQuality()) private val normalApis = arrayListOf(Shiro(), MultiQuality())
// https://gogo-stream.com/streaming.php?id=MTE3NDg5 // https://gogo-stream.com/streaming.php?id=MTE3NDg5

View file

@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.Qualities
class XStreamCdn : ExtractorApi() { class XStreamCdn : ExtractorApi() {
override val name: String = "XStreamCdn" override val name: String = "XStreamCdn"
override val mainUrl: String = "https://fcdn.stream" override val mainUrl: String = "https://fcdn.stream"
override val requiresReferer = true override val requiresReferer = false
private data class ResponseData( private data class ResponseData(
@JsonProperty("file") val file: String, @JsonProperty("file") val file: String,

View file

@ -2,15 +2,17 @@
<LinearLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<!--
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Loading..." android:textColor="?attr/textColor" android:textSize="20sp" android:text="Loading..." android:textColor="?attr/textColor" android:textSize="20sp"
android:textStyle="bold" android:layout_margin="10dp"/> android:textStyle="bold" android:layout_margin="10dp"/>-->
<androidx.core.widget.ContentLoadingProgressBar <!-- style="@android:style/Widget.Material.ProgressBar.Horizontal"
-->
<ProgressBar
android:layout_margin="10dp" android:layout_margin="10dp"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:indeterminate="true" android:layout_gravity="center"
android:layout_width="match_parent" android:layout_width="60dp"
android:layout_height="wrap_content" android:layout_height="60dp"
/> />
</LinearLayout> </LinearLayout>