forked from recloudstream/cloudstream
chromecast stuff
This commit is contained in:
parent
6579eb3083
commit
50abf2c74c
14 changed files with 323 additions and 118 deletions
|
@ -53,7 +53,7 @@ abstract class MainAPI {
|
|||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -169,8 +169,8 @@ data class AnimeLoadResponse(
|
|||
override val posterUrl: String?,
|
||||
override val year: Int?,
|
||||
|
||||
val dubEpisodes: ArrayList<Any>?,
|
||||
val subEpisodes: ArrayList<Any>?,
|
||||
val dubEpisodes: ArrayList<String>?,
|
||||
val subEpisodes: ArrayList<String>?,
|
||||
val showStatus: ShowStatus?,
|
||||
|
||||
override val plot: String?,
|
||||
|
|
|
@ -2,32 +2,27 @@ package com.lagradost.cloudstream3
|
|||
|
||||
import android.app.PictureInPictureParams
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
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.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
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
|
||||
import com.google.android.gms.cast.ApplicationMetadata
|
||||
import com.google.android.gms.cast.Cast
|
||||
import com.google.android.gms.cast.LaunchOptions
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
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.hasPIPPermission
|
||||
import com.lagradost.cloudstream3.UIHelper.requestRW
|
||||
import com.lagradost.cloudstream3.UIHelper.shouldShowPIPMode
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
/*, ViewModelStoreOwner {
|
||||
private val appViewModelStore: ViewModelStore by lazy {
|
||||
|
@ -110,8 +105,8 @@ class MainActivity : AppCompatActivity() {
|
|||
requestRW()
|
||||
if (checkWrite()) return
|
||||
}
|
||||
|
||||
CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
|
||||
|
||||
/*
|
||||
val castContext = CastContext.getSharedInstance(applicationContext)
|
||||
fun buildMediaQueueItem(video: String): MediaQueueItem {
|
||||
|
|
|
@ -178,7 +178,7 @@ class ShiroProvider : MainAPI() {
|
|||
val mapped = response.let { mapper.readValue<AnimePage>(it.text) }
|
||||
val data = mapped.data
|
||||
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) {
|
||||
"current" -> ShowStatus.Ongoing
|
||||
"finished" -> ShowStatus.Completed
|
||||
|
@ -205,12 +205,9 @@ class ShiroProvider : MainAPI() {
|
|||
)
|
||||
}
|
||||
|
||||
override fun loadLinks(data: Any, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
if (data is ShiroEpisodes) {
|
||||
return Vidstream().getUrl(data.videos[0].video_id, isCasting) {
|
||||
callback.invoke(it)
|
||||
}
|
||||
override fun loadLinks(data: String, isCasting: Boolean, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
return Vidstream().getUrl(data, isCasting) {
|
||||
callback.invoke(it)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -9,19 +9,25 @@ import androidx.appcompat.app.AlertDialog
|
|||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.google.android.gms.cast.MediaStatus.REPEAT_MODE_REPEAT_SINGLE
|
||||
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.CastButtonFactory
|
||||
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.widget.ExpandedControllerActivity
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromName
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
|
||||
import com.lagradost.cloudstream3.utils.Coroutines
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
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 java.lang.Exception
|
||||
|
||||
class SkipOpController(val view: ImageView) : UIController() {
|
||||
init {
|
||||
|
@ -32,8 +38,14 @@ class SkipOpController(val view: ImageView) : UIController() {
|
|||
}
|
||||
}
|
||||
|
||||
data class MetadataSource(val name: String)
|
||||
data class MetadataHolder(val data: List<MetadataSource>)
|
||||
data class MetadataHolder(
|
||||
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() {
|
||||
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
|
@ -42,30 +54,48 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
|||
init {
|
||||
view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
|
||||
view.setOnClickListener {
|
||||
//remoteMediaClient.mediaQueue.itemCount
|
||||
//println(remoteMediaClient.mediaInfo.customData)
|
||||
//remoteMediaClient.queueJumpToItem()
|
||||
lateinit var dialog: AlertDialog
|
||||
val holder = getCurrentMetaData()
|
||||
|
||||
if (holder != null) {
|
||||
val items = holder.data
|
||||
val items = holder.currentLinks
|
||||
if (items.isNotEmpty() && remoteMediaClient.currentItem != null) {
|
||||
val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
|
||||
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(
|
||||
items.map { it.name }.toTypedArray(),
|
||||
remoteMediaClient.mediaQueue.indexOfItemWithId(remoteMediaClient.currentItem.itemId)
|
||||
items.indexOfFirst { it.url == contentUrl }
|
||||
) { _, which ->
|
||||
val itemId = remoteMediaClient.mediaQueue.itemIds?.get(which)
|
||||
val epData = holder.episodes[holder.currentEpisodeIndex]
|
||||
|
||||
itemId?.let { id ->
|
||||
remoteMediaClient.queueJumpToItem(
|
||||
id,
|
||||
remoteMediaClient.approximateStreamPosition,
|
||||
remoteMediaClient.mediaInfo.customData
|
||||
)
|
||||
val mediaItem = getMediaInfo(epData,
|
||||
holder,
|
||||
holder.currentLinks[which],
|
||||
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()
|
||||
|
@ -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? {
|
||||
return try {
|
||||
val data = remoteMediaClient.mediaInfo.customData
|
||||
mapper.readValue<MetadataHolder>(data.toString())
|
||||
val data = remoteMediaClient?.mediaInfo?.customData?.toString()
|
||||
data?.toKotlinObject()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
var isLoadingMore = false
|
||||
|
||||
override fun onMediaStatusUpdated() {
|
||||
super.onMediaStatusUpdated()
|
||||
view.visibility =
|
||||
if ((getCurrentMetaData()?.data?.size
|
||||
?: 0) > 1
|
||||
) VISIBLE else INVISIBLE
|
||||
val meta = getCurrentMetaData()
|
||||
|
||||
view.visibility = if ((meta?.currentLinks?.size
|
||||
?: 0) > 1
|
||||
) 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?) {
|
||||
super.onSessionConnected(castSession)
|
||||
remoteMediaClient.queueSetRepeatMode(REPEAT_MODE_REPEAT_SINGLE, JSONObject())
|
||||
remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,9 +74,11 @@ import com.lagradost.cloudstream3.UIHelper.requestLocalAudioFocus
|
|||
import com.lagradost.cloudstream3.UIHelper.showSystemUI
|
||||
import com.lagradost.cloudstream3.UIHelper.toPx
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.mvvm.observeDirectly
|
||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||
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.setKey
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
@ -142,6 +144,8 @@ class PlayerFragment : Fragment() {
|
|||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||
|
||||
lateinit var apiName: String
|
||||
|
||||
private var isFullscreen = false
|
||||
private var isPlayerPlaying = true
|
||||
private lateinit var viewModel: ResultViewModel
|
||||
|
@ -733,7 +737,17 @@ class PlayerFragment : Fragment() {
|
|||
val epData = getEpisode() ?: return@addCastStateListener
|
||||
|
||||
val index = links.indexOf(getCurrentUrl())
|
||||
context?.startCast(
|
||||
apiName,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
epData.index,
|
||||
episodes,
|
||||
links,
|
||||
index,
|
||||
exoPlayer.currentPosition)
|
||||
|
||||
/*
|
||||
val customData =
|
||||
links.map { JSONObject().put("name", it.name) }
|
||||
val jsonArray = JSONArray()
|
||||
|
@ -773,8 +787,9 @@ class PlayerFragment : Fragment() {
|
|||
if (index > 0) index else 0,
|
||||
exoPlayer.currentPosition,
|
||||
MediaStatus.REPEAT_MODE_REPEAT_SINGLE
|
||||
)
|
||||
)*/
|
||||
// activity?.popCurrentPage(isInPlayer = true, isInExpandedView = false, isInResults = false)
|
||||
releasePlayer()
|
||||
activity?.popCurrentPage()
|
||||
}
|
||||
}
|
||||
|
@ -820,6 +835,10 @@ class PlayerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
observe(viewModel.apiName) {
|
||||
apiName = it
|
||||
}
|
||||
|
||||
overlay_loading_skip_button?.alpha = 0.5f
|
||||
observeDirectly(viewModel.allEpisodes) { _allEpisodes ->
|
||||
allEpisodes = _allEpisodes
|
||||
|
|
|
@ -20,11 +20,13 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
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.util.MimeTypes
|
||||
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.MediaStatus.REPEAT_MODE_REPEAT_SINGLE
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
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.ui.player.PlayerData
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerFragment
|
||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import jp.wasabeef.glide.transformations.BlurTransformation
|
||||
import kotlinx.android.synthetic.main.fragment_result.*
|
||||
|
@ -52,7 +55,7 @@ data class ResultEpisode(
|
|||
val poster: String?,
|
||||
val episode: Int,
|
||||
val season: Int?,
|
||||
val data: Any,
|
||||
val data: String,
|
||||
val apiName: String,
|
||||
val id: Int,
|
||||
val index: Int,
|
||||
|
@ -80,6 +83,7 @@ class ResultFragment : Fragment() {
|
|||
private lateinit var viewModel: ResultViewModel
|
||||
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
|
||||
var currentHeaderName: String? = null
|
||||
var currentEpisodes: ArrayList<ResultEpisode>? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -142,70 +146,79 @@ class ResultFragment : Fragment() {
|
|||
ArrayList(),
|
||||
result_episodes,
|
||||
) { episodeClick ->
|
||||
val id = episodeClick.data.id
|
||||
//val id = episodeClick.data.id
|
||||
val index = episodeClick.data.index
|
||||
val buildInPlayer = true
|
||||
when (episodeClick.action) {
|
||||
ACTION_CHROME_CAST_EPISODE -> {
|
||||
|
||||
/*
|
||||
val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom)
|
||||
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null);
|
||||
builder.setView(customLayout)
|
||||
|
||||
val dialog = builder.create()*/
|
||||
//dialog.show()
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
|
||||
|
||||
viewModel.loadEpisode(episodeClick.data, false) { data ->
|
||||
// dialog.dismiss()
|
||||
viewModel.loadEpisode(episodeClick.data, true) { data ->
|
||||
dialog.dismiss()
|
||||
when (data) {
|
||||
is Resource.Failure -> {
|
||||
Toast.makeText(activity, "Failed to load links", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
is Resource.Success -> {
|
||||
val epData = episodeClick.data
|
||||
val links = sortUrls(data.value)
|
||||
val eps = currentEpisodes ?: return@loadEpisode
|
||||
context?.startCast(
|
||||
apiName ?: return@loadEpisode,
|
||||
currentHeaderName,
|
||||
currentPoster,
|
||||
episodeClick.data.index,
|
||||
eps,
|
||||
sortUrls(data.value))
|
||||
/*
|
||||
val epData = episodeClick.data
|
||||
val links = sortUrls(data.value)
|
||||
|
||||
val castContext = CastContext.getSharedInstance(requireContext())
|
||||
val castContext = CastContext.getSharedInstance(requireContext())
|
||||
|
||||
val customData =
|
||||
links.map { JSONObject().put("name", it.name) }
|
||||
val jsonArray = JSONArray()
|
||||
for (item in customData) {
|
||||
jsonArray.put(item)
|
||||
}
|
||||
|
||||
val mediaItems = links.map {
|
||||
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
|
||||
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
|
||||
epData.name ?: "Episode ${epData.episode}")
|
||||
|
||||
if (currentHeaderName != null)
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, currentHeaderName)
|
||||
|
||||
val srcPoster = epData.poster ?: currentPoster
|
||||
if (srcPoster != null) {
|
||||
movieMetadata.addImage(WebImage(Uri.parse(srcPoster)))
|
||||
val customData =
|
||||
links.map { JSONObject().put("name", it.name) }
|
||||
val jsonArray = JSONArray()
|
||||
for (item in customData) {
|
||||
jsonArray.put(item)
|
||||
}
|
||||
MediaQueueItem.Builder(
|
||||
MediaInfo.Builder(it.url)
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
.setContentType(MimeTypes.VIDEO_UNKNOWN)
|
||||
.setCustomData(JSONObject().put("data", jsonArray))
|
||||
.setMetadata(movieMetadata)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}.toTypedArray()
|
||||
|
||||
val castPlayer = CastPlayer(castContext)
|
||||
castPlayer.loadItems(
|
||||
mediaItems,
|
||||
0,
|
||||
epData.progress,
|
||||
REPEAT_MODE_REPEAT_SINGLE
|
||||
)
|
||||
val mediaItems = links.map {
|
||||
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
|
||||
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
|
||||
epData.name ?: "Episode ${epData.episode}")
|
||||
|
||||
if (currentHeaderName != null)
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, currentHeaderName)
|
||||
|
||||
val srcPoster = epData.poster ?: currentPoster
|
||||
if (srcPoster != null) {
|
||||
movieMetadata.addImage(WebImage(Uri.parse(srcPoster)))
|
||||
}
|
||||
MediaQueueItem.Builder(
|
||||
MediaInfo.Builder(it.url)
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
.setContentType(MimeTypes.VIDEO_UNKNOWN)
|
||||
.setCustomData(JSONObject().put("data", jsonArray))
|
||||
.setMetadata(movieMetadata)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}.toTypedArray()
|
||||
|
||||
val castPlayer = CastPlayer(castContext)
|
||||
|
||||
castPlayer.loadItems(
|
||||
mediaItems,
|
||||
0,
|
||||
epData.progress,
|
||||
REPEAT_MODE_REPEAT_SINGLE
|
||||
//REPEAT_MODE_REPEAT_SINGLE
|
||||
)*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -244,6 +257,7 @@ class ResultFragment : Fragment() {
|
|||
observe(viewModel.episodes) { episodes ->
|
||||
if (result_episodes == null || result_episodes.adapter == null) return@observe
|
||||
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).notifyDataSetChanged()
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ class ResultViewModel : ViewModel() {
|
|||
d.apiName,
|
||||
(d.url).hashCode(),
|
||||
0,
|
||||
0, 0)))
|
||||
0, 0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +100,8 @@ class ResultViewModel : ViewModel() {
|
|||
val allEpisodes: LiveData<HashMap<Int, ArrayList<ExtractorLink>>> get() = _allEpisodes
|
||||
|
||||
private var _apiName: MutableLiveData<String> = MutableLiveData()
|
||||
val apiName: LiveData<String> get() = _apiName
|
||||
|
||||
|
||||
fun loadEpisode(
|
||||
episode: ResultEpisode,
|
||||
|
@ -110,7 +113,7 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
private fun loadEpisode(
|
||||
id: Int,
|
||||
data: Any,
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
callback: (Resource<ArrayList<ExtractorLink>>) -> Unit,
|
||||
) =
|
||||
|
@ -124,7 +127,6 @@ class ResultViewModel : ViewModel() {
|
|||
for (i in links) {
|
||||
if (i.url == it.url) return@loadLinks
|
||||
}
|
||||
println("LINK ADDED::::: " + it.url)
|
||||
|
||||
links.add(it)
|
||||
_allEpisodes.value?.set(id, links)
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.utils
|
|||
import com.lagradost.cloudstream3.utils.extractors.MixDrop
|
||||
import com.lagradost.cloudstream3.utils.extractors.Mp4Upload
|
||||
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
|
||||
|
||||
data class ExtractorLink(
|
||||
|
@ -19,8 +19,6 @@ fun ExtractorLink.getId() : Int {
|
|||
return url.hashCode()
|
||||
}
|
||||
|
||||
|
||||
|
||||
enum class Qualities(var value: Int) {
|
||||
Unknown(0),
|
||||
SD(-1), // 360p - 480p
|
||||
|
|
|
@ -6,7 +6,7 @@ class MixDrop : ExtractorApi() {
|
|||
override val name: String = "MixDrop"
|
||||
override val mainUrl: String = "https://mixdrop.co"
|
||||
private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""")
|
||||
override val requiresReferer = true
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/e/$id"
|
||||
|
|
|
@ -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.ExtractorLink
|
||||
|
@ -7,9 +7,9 @@ import com.lagradost.cloudstream3.utils.Qualities
|
|||
class StreamTape : ExtractorApi() {
|
||||
override val name: String = "StreamTape"
|
||||
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 =
|
||||
Regex("""(i(|" \+ ')d(|" \+ ')=.*?&(|" \+ ')e(|" \+ ')x(|" \+ ')p(|" \+ ')i(|" \+ ')r(|" \+ ')e(|" \+ ')s(|" \+ ')=.*?&(|" \+ ')i(|" \+ ')p(|" \+ ')=.*?&(|" \+ ')t(|" \+ ')o(|" \+ ')k(|" \+ ')e(|" \+ ')n(|" \+ ')=.*)'""")
|
||||
|
||||
|
|
|
@ -12,10 +12,11 @@ class Vidstream {
|
|||
private fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/streaming.php?id=$id"
|
||||
}
|
||||
|
||||
private val normalApis = arrayListOf(Shiro(), MultiQuality())
|
||||
|
||||
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
|
||||
fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit) : Boolean {
|
||||
fun getUrl(id: String, isCasting: Boolean = false, callback: (ExtractorLink) -> Unit): Boolean {
|
||||
try {
|
||||
normalApis.pmap { api ->
|
||||
val url = api.getExtractorUrl(id)
|
||||
|
@ -36,7 +37,7 @@ class Vidstream {
|
|||
//val name = element.text()
|
||||
|
||||
// Matches vidstream links with extractors
|
||||
extractorApis.filter { !it.requiresReferer || !isCasting}.pmap { api ->
|
||||
extractorApis.filter { !it.requiresReferer || !isCasting }.pmap { api ->
|
||||
if (link.startsWith(api.mainUrl)) {
|
||||
val extractedLinks = api.getUrl(link, url)
|
||||
if (extractedLinks?.isNotEmpty() == true) {
|
||||
|
|
|
@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.Qualities
|
|||
class XStreamCdn : ExtractorApi() {
|
||||
override val name: String = "XStreamCdn"
|
||||
override val mainUrl: String = "https://fcdn.stream"
|
||||
override val requiresReferer = true
|
||||
override val requiresReferer = false
|
||||
|
||||
private data class ResponseData(
|
||||
@JsonProperty("file") val file: String,
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
<LinearLayout android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!--
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:text="Loading..." android:textColor="?attr/textColor" android:textSize="20sp"
|
||||
android:textStyle="bold" android:layout_margin="10dp"/>
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:textStyle="bold" android:layout_margin="10dp"/>-->
|
||||
<!-- style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
-->
|
||||
<ProgressBar
|
||||
android:layout_margin="10dp"
|
||||
style="@android:style/Widget.Material.ProgressBar.Horizontal"
|
||||
android:indeterminate="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
/>
|
||||
</LinearLayout>
|
Loading…
Reference in a new issue