mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
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
|
// 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?,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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(|" \+ ')=.*)'""")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue