mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
chromecast bug fix
This commit is contained in:
parent
50abf2c74c
commit
efeb42fd2e
3 changed files with 125 additions and 53 deletions
|
@ -2,8 +2,7 @@ package com.lagradost.cloudstream3.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.View.INVISIBLE
|
import android.view.View.*
|
||||||
import android.view.View.VISIBLE
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
@ -13,6 +12,7 @@ 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_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.RemoteMediaClient
|
||||||
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.APIHolder.getApiFromName
|
||||||
|
@ -21,6 +21,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||||
import com.lagradost.cloudstream3.sortUrls
|
import com.lagradost.cloudstream3.sortUrls
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
|
import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks
|
||||||
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
|
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||||
|
@ -38,6 +39,37 @@ class SkipOpController(val view: ImageView) : UIController() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RemoteMediaClient.getItemIndex(): Int? {
|
||||||
|
return try {
|
||||||
|
val index = this.mediaQueue?.itemIds?.indexOf(this.currentItem?.itemId ?: 0)
|
||||||
|
if (index == null || index < 0) null else index
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SkipNextEpisodeController(val view: ImageView) : UIController() {
|
||||||
|
init {
|
||||||
|
view.setImageResource(R.drawable.exo_controls_fastforward)
|
||||||
|
view.setOnClickListener {
|
||||||
|
remoteMediaClient?.let {
|
||||||
|
it.queueNext(JSONObject())
|
||||||
|
view.visibility = GONE // TO PREVENT MULTI CLICK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaStatusUpdated() {
|
||||||
|
super.onMediaStatusUpdated()
|
||||||
|
view.visibility = GONE
|
||||||
|
val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return
|
||||||
|
val itemCount = remoteMediaClient?.mediaQueue?.itemCount ?: return
|
||||||
|
if (itemCount - currentIdIndex > 1 && remoteMediaClient?.isLoadingNextItem == false) {
|
||||||
|
view.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class MetadataHolder(
|
data class MetadataHolder(
|
||||||
val apiName: String,
|
val apiName: String,
|
||||||
val title: String?,
|
val title: String?,
|
||||||
|
@ -59,13 +91,13 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
|
|
||||||
if (holder != null) {
|
if (holder != null) {
|
||||||
val items = holder.currentLinks
|
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
|
//https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages.MediaInformation
|
||||||
val contentUrl = (remoteMediaClient.currentItem.media.contentUrl
|
val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl
|
||||||
?: remoteMediaClient.currentItem.media.contentId)
|
?: remoteMediaClient?.currentItem?.media?.contentId)
|
||||||
|
|
||||||
builder.setSingleChoiceItems(
|
builder.setSingleChoiceItems(
|
||||||
items.map { it.name }.toTypedArray(),
|
items.map { it.name }.toTypedArray(),
|
||||||
|
@ -73,30 +105,44 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
) { _, which ->
|
) { _, which ->
|
||||||
val epData = holder.episodes[holder.currentEpisodeIndex]
|
val epData = holder.episodes[holder.currentEpisodeIndex]
|
||||||
|
|
||||||
val mediaItem = getMediaInfo(epData,
|
fun loadMirror(index: Int) {
|
||||||
|
if (holder.currentLinks.size <= index) return
|
||||||
|
|
||||||
|
val mediaItem = getMediaInfo(
|
||||||
|
epData,
|
||||||
holder,
|
holder,
|
||||||
holder.currentLinks[which],
|
index,
|
||||||
remoteMediaClient.mediaInfo.customData)
|
remoteMediaClient?.mediaInfo?.customData)
|
||||||
|
|
||||||
val startAt = remoteMediaClient.approximateStreamPosition
|
val startAt = remoteMediaClient?.approximateStreamPosition ?: 0
|
||||||
|
|
||||||
try {
|
//remoteMediaClient.load(mediaItem, true, startAt)
|
||||||
|
try { // THIS IS VERY IMPORTANT BECAUSE WE NEVER WANT TO AUTOLOAD THE NEXT EPISODE
|
||||||
|
val currentIdIndex = remoteMediaClient?.getItemIndex()
|
||||||
|
|
||||||
val currentIdIndex = getItemIndex() ?: return@setSingleChoiceItems
|
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex?.plus(1) ?: 0)
|
||||||
|
|
||||||
val nextId = remoteMediaClient.mediaQueue.itemIds?.get(currentIdIndex + 1)
|
if (currentIdIndex == null && nextId != null) {
|
||||||
|
awaitLinks(remoteMediaClient?.queueInsertAndPlayItem(MediaQueueItem.Builder(
|
||||||
if (nextId != null) {
|
mediaItem)
|
||||||
remoteMediaClient.queueInsertAndPlayItem(MediaQueueItem.Builder(mediaItem).build(),
|
.build(),
|
||||||
nextId,
|
nextId,
|
||||||
startAt,
|
startAt,
|
||||||
JSONObject())
|
JSONObject())) {
|
||||||
|
loadMirror(index + 1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
remoteMediaClient.load(mediaItem, true, startAt)
|
awaitLinks(remoteMediaClient?.load(mediaItem, true, startAt)) {
|
||||||
|
loadMirror(index + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
remoteMediaClient.load(mediaItem, true, startAt)
|
awaitLinks(remoteMediaClient?.load(mediaItem, true, startAt)) {
|
||||||
|
loadMirror(index + 1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadMirror(which)
|
||||||
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -107,11 +153,6 @@ 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?.toString()
|
val data = remoteMediaClient?.mediaInfo?.customData?.toString()
|
||||||
|
@ -133,7 +174,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
try {
|
try {
|
||||||
if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) {
|
if (meta != null && meta.episodes.size > meta.currentEpisodeIndex + 1) {
|
||||||
|
|
||||||
val currentIdIndex = getItemIndex() ?: return
|
val currentIdIndex = remoteMediaClient?.getItemIndex() ?: return
|
||||||
val itemCount = remoteMediaClient?.mediaQueue?.itemCount
|
val itemCount = remoteMediaClient?.mediaQueue?.itemCount
|
||||||
|
|
||||||
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
|
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
|
||||||
|
@ -152,19 +193,41 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
||||||
links.add(it)
|
links.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res is Resource.Success) {
|
if (res is Resource.Success) {
|
||||||
val sorted = sortUrls(links)
|
val sorted = sortUrls(links)
|
||||||
if (sorted.isNotEmpty()) {
|
if (sorted.isNotEmpty()) {
|
||||||
val jsonCopy = meta.copy(currentLinks = sorted, currentEpisodeIndex = index)
|
val jsonCopy = meta.copy(currentLinks = sorted, currentEpisodeIndex = index)
|
||||||
|
|
||||||
val done = withContext(Dispatchers.IO) {
|
val done = withContext(Dispatchers.IO) {
|
||||||
getMediaInfo(epData,
|
JSONObject(mapper.writeValueAsString(jsonCopy))
|
||||||
meta,
|
}
|
||||||
sorted.first(),
|
|
||||||
JSONObject(mapper.writeValueAsString(jsonCopy)))
|
val mediaInfo = getMediaInfo(
|
||||||
|
epData,
|
||||||
|
jsonCopy,
|
||||||
|
0,
|
||||||
|
done)
|
||||||
|
|
||||||
|
/*fun loadIndex(index: Int) {
|
||||||
|
println("LOAD INDEX::::: $index")
|
||||||
|
if (meta.currentLinks.size <= index) return
|
||||||
|
val info = getMediaInfo(
|
||||||
|
epData,
|
||||||
|
meta,
|
||||||
|
index,
|
||||||
|
done)
|
||||||
|
awaitLinks(remoteMediaClient?.load(info, true, 0)) {
|
||||||
|
loadIndex(index + 1)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
awaitLinks(remoteMediaClient?.queueAppendItem(MediaQueueItem.Builder(mediaInfo).build(),
|
||||||
|
JSONObject())) {
|
||||||
|
println("FAILED TO LOAD NEXT ITEM")
|
||||||
|
// loadIndex(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteMediaClient?.queueAppendItem(MediaQueueItem.Builder(done).build(), JSONObject())
|
|
||||||
isLoadingMore = false
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,6 +275,6 @@ class ControllerActivity : ExpandedControllerActivity() {
|
||||||
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton, this))
|
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton, this))
|
||||||
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
|
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
|
||||||
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
|
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
|
||||||
uiMediaController.bindViewToUIController(skipOpButton, SkipOpController(skipOpButton))
|
uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -517,10 +517,10 @@ class PlayerFragment : Fragment() {
|
||||||
val alphaAnimation = AlphaAnimation(0f, 1f)
|
val alphaAnimation = AlphaAnimation(0f, 1f)
|
||||||
alphaAnimation.duration = 100
|
alphaAnimation.duration = 100
|
||||||
alphaAnimation.fillAfter = true
|
alphaAnimation.fillAfter = true
|
||||||
video_go_back_holder.visibility = VISIBLE
|
video_go_back_holder?.visibility = VISIBLE
|
||||||
|
|
||||||
overlay_loading_skip_button.visibility = VISIBLE
|
overlay_loading_skip_button?.visibility = VISIBLE
|
||||||
loading_overlay.startAnimation(alphaAnimation)
|
loading_overlay?.startAnimation(alphaAnimation)
|
||||||
if (this::exoPlayer.isInitialized) {
|
if (this::exoPlayer.isInitialized) {
|
||||||
isPlayerPlaying = exoPlayer.playWhenReady
|
isPlayerPlaying = exoPlayer.playWhenReady
|
||||||
playbackPosition = exoPlayer.currentPosition
|
playbackPosition = exoPlayer.currentPosition
|
||||||
|
@ -555,11 +555,10 @@ class PlayerFragment : Fragment() {
|
||||||
|
|
||||||
private fun savePos() {
|
private fun savePos() {
|
||||||
if (this::exoPlayer.isInitialized) {
|
if (this::exoPlayer.isInitialized) {
|
||||||
/*if (
|
if (exoPlayer.duration > 0 && exoPlayer.currentPosition > 0) {
|
||||||
&& exoPlayer.duration > 0 && exoPlayer.currentPosition > 0
|
//TODO FIX SAVE POS
|
||||||
) {
|
// setViewPosDur(data!!, exoPlayer.currentPosition, exoPlayer.duration)
|
||||||
setViewPosDur(data!!, exoPlayer.currentPosition, exoPlayer.duration)
|
}
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,11 +1174,10 @@ class PlayerFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO FIX NON PIP MODE BUG
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
isInPlayer = false
|
isInPlayer = false
|
||||||
// releasePlayer()
|
releasePlayer()
|
||||||
|
|
||||||
activity?.showSystemUI()
|
activity?.showSystemUI()
|
||||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
|
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER
|
||||||
|
@ -1247,6 +1245,7 @@ class PlayerFragment : Fragment() {
|
||||||
hasUsedFirstRender = false
|
hasUsedFirstRender = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!isInPlayer) return
|
||||||
if (this::exoPlayer.isInitialized) {
|
if (this::exoPlayer.isInitialized) {
|
||||||
savePos()
|
savePos()
|
||||||
exoPlayer.release()
|
exoPlayer.release()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.utils
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
@ -19,7 +20,9 @@ import com.google.android.gms.common.images.WebImage
|
||||||
import com.lagradost.cloudstream3.ui.MetadataHolder
|
import com.lagradost.cloudstream3.ui.MetadataHolder
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
@ -27,7 +30,8 @@ object CastHelper {
|
||||||
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||||
|
|
||||||
fun getMediaInfo(epData: ResultEpisode, holder: MetadataHolder, link: ExtractorLink, data: JSONObject?): MediaInfo {
|
fun getMediaInfo(epData: ResultEpisode, holder: MetadataHolder, index: Int, data: JSONObject?): MediaInfo {
|
||||||
|
val link = holder.currentLinks[index]
|
||||||
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
|
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
|
||||||
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
|
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE,
|
||||||
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
|
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}")
|
||||||
|
@ -47,20 +51,22 @@ object CastHelper {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun awaitLinks(pending: PendingResult<RemoteMediaClient.MediaChannelResult>?) {
|
fun awaitLinks(pending: PendingResult<RemoteMediaClient.MediaChannelResult>?, callback: (Boolean) -> Unit) {
|
||||||
if (pending == null) return
|
if (pending == null) return
|
||||||
thread {
|
main {
|
||||||
val res = pending.await()
|
val res = withContext(Dispatchers.IO) { pending.await() }
|
||||||
when (res.status?.statusCode) {
|
when (res.status?.statusCode) {
|
||||||
CastStatusCodes.FAILED -> {
|
CastStatusCodes.FAILED -> {
|
||||||
println("FAILED WITH DATA: " + res.customData)
|
callback.invoke(true)
|
||||||
|
println("FAILED AND LOAD NEXT")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
println("FAILED::: " + res.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.startCast(
|
fun Context.startCast(
|
||||||
apiName: String,
|
apiName: String,
|
||||||
|
@ -80,16 +86,20 @@ object CastHelper {
|
||||||
|
|
||||||
val holder = MetadataHolder(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks)
|
val holder = MetadataHolder(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks)
|
||||||
|
|
||||||
|
val index = startIndex ?: 0
|
||||||
val mediaItem =
|
val mediaItem =
|
||||||
getMediaInfo(epData, holder, currentLinks[startIndex ?: 0], JSONObject(mapper.writeValueAsString(holder)))
|
getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder)))
|
||||||
|
|
||||||
val castPlayer = CastPlayer(castContext)
|
val castPlayer = CastPlayer(castContext)
|
||||||
|
|
||||||
castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF
|
castPlayer.repeatMode = REPEAT_MODE_REPEAT_OFF
|
||||||
castPlayer.stop()
|
|
||||||
awaitLinks(castPlayer.loadItem(
|
awaitLinks(castPlayer.loadItem(
|
||||||
MediaQueueItem.Builder(mediaItem).build(),
|
MediaQueueItem.Builder(mediaItem).build(),
|
||||||
startTime ?: 0,
|
startTime ?: 0,
|
||||||
))
|
)) {
|
||||||
|
if (currentLinks.size > index + 1)
|
||||||
|
startCast(apiName, title, poster, currentEpisodeIndex, episodes, currentLinks, index + 1, startTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue