lots of chromecast stuff for better UI

This commit is contained in:
LagradOst 2021-06-14 02:00:29 +02:00
parent ac442edd4e
commit 6579eb3083
12 changed files with 207 additions and 66 deletions

View file

@ -2,6 +2,7 @@ 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
@ -12,10 +13,20 @@ 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.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
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 {
@ -99,5 +110,32 @@ class MainActivity : AppCompatActivity() {
requestRW()
if (checkWrite()) return
}
CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
/*
val castContext = CastContext.getSharedInstance(applicationContext)
fun buildMediaQueueItem(video: String): MediaQueueItem {
// val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO)
//movieMetadata.putString(MediaMetadata.KEY_TITLE, "CloudStream")
val mediaInfo = MediaInfo.Builder(Uri.parse(video).toString())
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
.setContentType(MimeTypes.IMAGE_JPEG)
// .setMetadata(movieMetadata).build()
.build()
return MediaQueueItem.Builder(mediaInfo).build()
}*/
/*
castContext.addCastStateListener { state ->
if (state == CastState.CONNECTED) {
println("TESTING")
val isCasting = castContext?.sessionManager?.currentCastSession?.remoteMediaClient?.currentItem != null
if(!isCasting) {
val castPlayer = CastPlayer(castContext)
println("LOAD ITEM")
castPlayer.loadItem(buildMediaQueueItem("https://cdn.discordapp.com/attachments/551382684560261121/730169809408622702/ChromecastLogo6.png"),0)
}
}
}*/
}
}

View file

@ -6,6 +6,7 @@ import android.app.AppOpsManager
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Color
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
@ -13,6 +14,7 @@ import android.os.Build
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@ -21,9 +23,10 @@ import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.lagradost.cloudstream3.UIHelper.getGridFormat
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.Event
import kotlin.math.roundToInt
object UIHelper {
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
@ -143,6 +146,21 @@ object UIHelper {
return isCastApiAvailable
}
fun adjustAlpha(@ColorInt color: Int, factor: Float): Int {
val alpha = (Color.alpha(color) * factor).roundToInt()
val red = Color.red(color)
val green = Color.green(color)
val blue = Color.blue(color)
return Color.argb(alpha, red, green, blue)
}
fun Context.colorFromAttribute(attribute: Int): Int {
val attributes = obtainStyledAttributes(intArrayOf(attribute))
val color = attributes.getColor(0, 0)
attributes.recycle()
return color
}
fun getFocusRequest(): AudioFocusRequest? {
if (_AudioFocusRequest != null) return _AudioFocusRequest
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View file

@ -6,14 +6,22 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.widget.ImageView
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.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.R
import com.lagradost.cloudstream3.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.Coroutines
import kotlinx.coroutines.delay
import org.json.JSONObject
import java.lang.Exception
class SkipOpController(val view: ImageView) : UIController() {
init {
@ -24,7 +32,12 @@ class SkipOpController(val view: ImageView) : UIController() {
}
}
class SelectSourceController(val view: ImageView) : UIController() {
data class MetadataSource(val name: String)
data class MetadataHolder(val data: List<MetadataSource>)
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
init {
view.setImageResource(R.drawable.ic_baseline_playlist_play_24)
@ -33,31 +46,28 @@ class SelectSourceController(val view: ImageView) : UIController() {
//println(remoteMediaClient.mediaInfo.customData)
//remoteMediaClient.queueJumpToItem()
lateinit var dialog: AlertDialog
val items = mutableListOf<Pair<Int, String>>()
for (i in 0 until remoteMediaClient.mediaQueue.itemCount) {
(remoteMediaClient.mediaQueue.getItemAtIndex(i)?.media?.customData?.get("data") as? String)?.let { name ->
items.add(
remoteMediaClient.mediaQueue.getItemAtIndex(i)!!.itemId to name
)
}
}
val holder = getCurrentMetaData()
// TODO FIX
if (items.isNotEmpty()) {
if (holder != null) {
val items = holder.data
if (items.isNotEmpty() && remoteMediaClient.currentItem != null) {
val builder = AlertDialog.Builder(view.context, R.style.AlertDialogCustom)
builder.setTitle("Pick source")
builder.setSingleChoiceItems(
items.map { it.second }.toTypedArray(),
remoteMediaClient.currentItem.itemId - 1
items.map { it.name }.toTypedArray(),
remoteMediaClient.mediaQueue.indexOfItemWithId(remoteMediaClient.currentItem.itemId)
) { _, which ->
println(
val itemId = remoteMediaClient.mediaQueue.itemIds?.get(which)
itemId?.let { id ->
remoteMediaClient.queueJumpToItem(
items[which].first,
id,
remoteMediaClient.approximateStreamPosition,
null
)
remoteMediaClient.mediaInfo.customData
)
}
dialog.dismiss()
}
dialog = builder.create()
@ -65,13 +75,23 @@ class SelectSourceController(val view: ImageView) : UIController() {
}
}
}
}
private fun getCurrentMetaData(): MetadataHolder? {
return try {
val data = remoteMediaClient.mediaInfo.customData
mapper.readValue<MetadataHolder>(data.toString())
} catch (e: Exception) {
null
}
}
override fun onMediaStatusUpdated() {
super.onMediaStatusUpdated()
// If there's 1 item it won't show
val dataString = remoteMediaClient.mediaQueue.getItemAtIndex(1)?.media?.customData?.get("data") as? String
view.visibility = if (dataString != null) VISIBLE else INVISIBLE
view.visibility =
if ((getCurrentMetaData()?.data?.size
?: 0) > 1
) VISIBLE else INVISIBLE
}
override fun onSessionConnected(castSession: CastSession?) {
@ -107,7 +127,7 @@ class ControllerActivity : ExpandedControllerActivity() {
val skipBackButton: ImageView = getButtonImageViewAt(1)
val skipForwardButton: ImageView = getButtonImageViewAt(2)
val skipOpButton: ImageView = getButtonImageViewAt(3)
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton))
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton, this))
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
uiMediaController.bindViewToUIController(skipOpButton, SkipOpController(skipOpButton))

View file

@ -0,0 +1,37 @@
package com.lagradost.cloudstream3.ui
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.RelativeLayout
import com.google.android.gms.cast.framework.media.widget.MiniControllerFragment
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.adjustAlpha
import com.lagradost.cloudstream3.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.UIHelper.toPx
class MyMiniControllerFragment : MiniControllerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// SEE https://github.com/dandar3/android-google-play-services-cast-framework/blob/master/res/layout/cast_mini_controller.xml
try {
val progressBar: ProgressBar? = view.findViewById(R.id.progressBar)
val containerAll: LinearLayout? = view.findViewById(R.id.container_all)
context?.let { ctx ->
progressBar?.setBackgroundColor(adjustAlpha(ctx.colorFromAttribute(R.attr.colorPrimary), 0.35f))
val params = RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 2.toPx)
progressBar?.layoutParams = params
}
val child = containerAll?.getChildAt(0)
child?.alpha = 0f // REMOVE GRADIENT
} catch (e: Exception) {
// JUST IN CASE
}
}
}

View file

@ -84,6 +84,7 @@ import com.lagradost.cloudstream3.utils.getId
import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.*
import kotlinx.coroutines.*
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import javax.net.ssl.HttpsURLConnection
@ -608,6 +609,7 @@ class PlayerFragment : Fragment() {
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
private var episodes: ArrayList<ResultEpisode> = ArrayList()
var currentPoster: String? = null
var currentHeaderName: String? = null
//region PIP MODE
private fun getPen(code: PlayerEventType): PendingIntent {
@ -717,7 +719,7 @@ class PlayerFragment : Fragment() {
if (activity?.isCastApiAvailable() == true) {
CastButtonFactory.setUpMediaRouteButton(activity, player_media_route_button)
val castContext = CastContext.getSharedInstance(requireActivity().applicationContext)
val castContext = CastContext.getSharedInstance(requireContext())
if (castContext.castState != CastState.NO_DEVICES_AVAILABLE) player_media_route_button.visibility = VISIBLE
castContext.addCastStateListener { state ->
@ -732,20 +734,22 @@ class PlayerFragment : Fragment() {
val index = links.indexOf(getCurrentUrl())
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_TITLE,
"Episode ${epData.episode}" +
if (epData.name != null)
"- ${epData.name}"
else
""
)
movieMetadata.putString(MediaMetadata.KEY_ALBUM_ARTIST,
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)))
@ -755,7 +759,8 @@ class PlayerFragment : Fragment() {
MediaInfo.Builder(it.url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.VIDEO_UNKNOWN)
.setCustomData(JSONObject().put("data", it.name))
.setCustomData(JSONObject().put("data", jsonArray))
.setMetadata(movieMetadata)
.build()
)
@ -838,6 +843,7 @@ class PlayerFragment : Fragment() {
if (d is LoadResponse) {
localData = d
currentPoster = d.posterUrl
currentHeaderName = d.name
}
}
is Resource.Failure -> {

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
@ -16,7 +15,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.mediarouter.app.MediaRouteButton
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
@ -33,6 +31,7 @@ import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.images.WebImage
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable
import com.lagradost.cloudstream3.mvvm.Resource
@ -42,9 +41,9 @@ import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.utils.ExtractorLink
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
import org.json.JSONArray
import org.json.JSONObject
import android.app.ProgressDialog
import com.lagradost.cloudstream3.*
const val MAX_SYNO_LENGH = 300
@ -80,6 +79,7 @@ class ResultFragment : Fragment() {
private lateinit var viewModel: ResultViewModel
private var allEpisodes: HashMap<Int, ArrayList<ExtractorLink>> = HashMap()
var currentHeaderName: String? = null
override fun onCreateView(
inflater: LayoutInflater,
@ -157,7 +157,7 @@ class ResultFragment : Fragment() {
//dialog.show()
Toast.makeText(activity, "Loading links", Toast.LENGTH_SHORT).show()
viewModel.loadEpisode(episodeClick.data, true) { data ->
viewModel.loadEpisode(episodeClick.data, false) { data ->
// dialog.dismiss()
when (data) {
is Resource.Failure -> {
@ -169,30 +169,30 @@ class ResultFragment : Fragment() {
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_TITLE,
"Episode ${epData.episode}" +
if (epData.name != null)
"- ${epData.name}"
else
""
)
movieMetadata.putString(MediaMetadata.KEY_ALBUM_ARTIST,
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", it.name))
.setCustomData(JSONObject().put("data", jsonArray))
.setMetadata(movieMetadata)
.build()
)
@ -255,6 +255,8 @@ class ResultFragment : Fragment() {
if (d is LoadResponse) {
result_bookmark_button.text = "Watching"
currentHeaderName = d.name
currentPoster = d.posterUrl
result_openinbrower.setOnClickListener {

View file

@ -38,7 +38,8 @@ class CastOptionsProvider : OptionsProvider {
return CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
//.setReceiverApplicationId("C0868879") // C0868879 = SAMPLE, CHANGE TO A NICE ID at https://developers.google.com/cast/docs/registration
//.setReceiverApplicationId("")
// C0868879 = SAMPLE, CHANGE TO A NICE ID at https://developers.google.com/cast/docs/registration
.setStopReceiverApplicationWhenEndingSession(true)
.setCastMediaOptions(mediaOptions)
.build()

View file

@ -0,0 +1,13 @@
package com.lagradost.cloudstream3.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object Coroutines {
fun main(work: suspend (() -> Unit)) {
CoroutineScope(Dispatchers.Main).launch {
work()
}
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorPrimary"/>
</shape>

View file

@ -42,13 +42,14 @@
app:layout_constraintBottom_toTopOf="@+id/nav_view"
android:id="@+id/cast_mini_controller_holder"
>
<!--com.google.android.gms.cast.framework.media.widget.MiniControllerFragment-->
<fragment
app:castControlButtons="@array/cast_mini_controller_control_buttons"
android:id="@+id/cast_mini_controller"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
class="com.lagradost.cloudstream3.ui.MyMiniControllerFragment"
tools:ignore="FragmentTagUsage">
</fragment>
</LinearLayout>

View file

@ -229,7 +229,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
class="com.lagradost.cloudstream3.ui.MyMiniControllerFragment"
tools:ignore="FragmentTagUsage">
</fragment>
</LinearLayout>

View file

@ -99,6 +99,7 @@
<item name="tabMode">scrollable</item>
</style>
<style name="AlertDialogCustom" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:windowFullscreen">true</item>
<item name="android:textColor">@color/textColor</item>
<item name="android:textColorPrimary">@color/textColor</item>
<!--<item name="android:background">@color/darkBackground</item>-->