native casting support
This commit is contained in:
parent
bf908c5e37
commit
e83a580486
6 changed files with 132 additions and 19 deletions
|
@ -90,6 +90,9 @@ dependencies {
|
|||
implementation "androidx.media3:media3-exoplayer-dash:$exo_version"
|
||||
implementation "androidx.media3:media3-datasource-okhttp:$exo_version"
|
||||
implementation "androidx.media3:media3-session:$exo_version"
|
||||
//media3 casting
|
||||
implementation "androidx.media3:media3-cast:$exo_version"
|
||||
implementation "androidx.mediarouter:mediarouter:1.6.0"
|
||||
|
||||
// UI
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"
|
||||
tools:ignore="LeanbackUsesWifi" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
@ -299,6 +301,9 @@
|
|||
android:name=".connections.discord.DiscordService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,43 @@
|
|||
package ani.dantotsu.media.anime
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.mediarouter.app.MediaRouteActionProvider
|
||||
import androidx.mediarouter.app.MediaRouteChooserDialog
|
||||
import androidx.mediarouter.app.MediaRouteChooserDialogFragment
|
||||
import androidx.mediarouter.app.MediaRouteControllerDialog
|
||||
import androidx.mediarouter.app.MediaRouteControllerDialogFragment
|
||||
import androidx.mediarouter.app.MediaRouteDialogFactory
|
||||
import ani.dantotsu.R
|
||||
|
||||
class CustomCastProvider(context: Context) : MediaRouteActionProvider(context) {
|
||||
init {
|
||||
dialogFactory = CustomCastThemeFactory()
|
||||
}
|
||||
}
|
||||
|
||||
class CustomCastThemeFactory : MediaRouteDialogFactory() {
|
||||
override fun onCreateChooserDialogFragment(): MediaRouteChooserDialogFragment {
|
||||
return CustomMediaRouterChooserDialogFragment()
|
||||
}
|
||||
|
||||
override fun onCreateControllerDialogFragment(): MediaRouteControllerDialogFragment {
|
||||
return CustomMediaRouteControllerDialogFragment()
|
||||
}
|
||||
}
|
||||
|
||||
class CustomMediaRouterChooserDialogFragment: MediaRouteChooserDialogFragment() {
|
||||
override fun onCreateChooserDialog(
|
||||
context: Context,
|
||||
savedInstanceState: Bundle?
|
||||
): MediaRouteChooserDialog =
|
||||
MediaRouteChooserDialog(context)
|
||||
}
|
||||
|
||||
class CustomMediaRouteControllerDialogFragment: MediaRouteControllerDialogFragment() {
|
||||
override fun onCreateControllerDialog(
|
||||
context: Context,
|
||||
savedInstanceState: Bundle?
|
||||
): MediaRouteControllerDialog =
|
||||
MediaRouteControllerDialog(context, R.style.ThemeOverlay_Dantotsu_MediaRouter)
|
||||
}
|
|
@ -14,7 +14,6 @@ import android.content.pm.PackageManager
|
|||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorManager
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.*
|
||||
|
@ -96,11 +95,15 @@ import java.util.concurrent.*
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
import androidx.media3.cast.SessionAvailabilityListener
|
||||
import androidx.media3.cast.CastPlayer
|
||||
import androidx.mediarouter.app.MediaRouteButton
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
|
||||
@UnstableApi
|
||||
@SuppressLint("SetTextI18n", "ClickableViewAccessibility")
|
||||
class ExoplayerView : AppCompatActivity(), Player.Listener {
|
||||
class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener {
|
||||
|
||||
private val resumeWindow = "resumeWindow"
|
||||
private val resumePosition = "resumePosition"
|
||||
|
@ -108,6 +111,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
private val playerOnPlay = "playerOnPlay"
|
||||
|
||||
private lateinit var exoPlayer: ExoPlayer
|
||||
private lateinit var castPlayer: CastPlayer
|
||||
private lateinit var castContext: CastContext
|
||||
private lateinit var trackSelector: DefaultTrackSelector
|
||||
private lateinit var cacheFactory: CacheDataSource.Factory
|
||||
private lateinit var playbackParameters: PlaybackParameters
|
||||
|
@ -328,6 +333,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
setContentView(binding.root)
|
||||
|
||||
//Initialize
|
||||
|
||||
castContext = CastContext.getSharedInstance(this)
|
||||
castPlayer = CastPlayer(castContext)
|
||||
castPlayer.setSessionAvailabilityListener(this)
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
hideSystemBars()
|
||||
|
||||
|
@ -387,7 +397,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
orientationListener =
|
||||
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
println(orientation)
|
||||
if (orientation in 45..135) {
|
||||
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
|
||||
View.VISIBLE
|
||||
|
@ -466,15 +475,21 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
if (isInitialized) {
|
||||
isPlayerPlaying = exoPlayer.isPlaying
|
||||
(exoPlay.drawable as Animatable?)?.start()
|
||||
if (isPlayerPlaying) {
|
||||
if (isPlayerPlaying || castPlayer.isPlaying ) {
|
||||
Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay)
|
||||
exoPlayer.pause()
|
||||
castPlayer.pause()
|
||||
} else {
|
||||
if (!castPlayer.isPlaying && castPlayer.currentMediaItem != null) {
|
||||
Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
|
||||
castPlayer.play()
|
||||
} else if (!isPlayerPlaying) {
|
||||
Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
|
||||
exoPlayer.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Picture-in-picture
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
|
@ -1074,11 +1089,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
|
||||
//Cast
|
||||
if (settings.cast) {
|
||||
playerView.findViewById<ImageButton>(R.id.exo_cast).apply {
|
||||
playerView.findViewById<MediaRouteButton>(R.id.exo_cast).apply {
|
||||
visibility = View.VISIBLE
|
||||
setSafeOnClickListener {
|
||||
cast()
|
||||
}
|
||||
CastButtonFactory.setUpMediaRouteButton(context, this)
|
||||
dialogFactory = CustomCastThemeFactory()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1483,7 +1497,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
super.onPause()
|
||||
orientationListener?.disable()
|
||||
if (isInitialized) {
|
||||
if (!castPlayer.isPlaying) {
|
||||
playerView.player?.pause()
|
||||
}
|
||||
saveData(
|
||||
"${media.id}_${media.anime!!.selectedEpisode}",
|
||||
exoPlayer.currentPosition,
|
||||
|
@ -1504,7 +1520,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
}
|
||||
|
||||
override fun onStop() {
|
||||
if (!castPlayer.isPlaying) {
|
||||
playerView.player?.pause()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
@ -1797,7 +1815,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
|
||||
// Enter PiP Mode
|
||||
@Suppress("DEPRECATION")
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
private fun enterPipMode() {
|
||||
wasPlaying = isPlayerPlaying
|
||||
if (!pipEnabled) return
|
||||
|
@ -1870,6 +1887,47 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private fun startCastPlayer() {
|
||||
castPlayer.setMediaItem(mediaItem)
|
||||
castPlayer.prepare()
|
||||
playerView.player = castPlayer
|
||||
exoPlayer.stop()
|
||||
castPlayer.addListener(object : Player.Listener {
|
||||
//if the player is paused changed, we want to update the UI
|
||||
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
|
||||
super.onPlayWhenReadyChanged(playWhenReady, reason)
|
||||
if (playWhenReady) {
|
||||
(exoPlay.drawable as Animatable?)?.start()
|
||||
Glide.with(this@ExoplayerView)
|
||||
.load(R.drawable.anim_play_to_pause)
|
||||
.into(exoPlay)
|
||||
} else {
|
||||
(exoPlay.drawable as Animatable?)?.start()
|
||||
Glide.with(this@ExoplayerView)
|
||||
.load(R.drawable.anim_pause_to_play)
|
||||
.into(exoPlay)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun startExoPlayer() {
|
||||
exoPlayer.setMediaItem(mediaItem)
|
||||
exoPlayer.prepare()
|
||||
playerView.player = exoPlayer
|
||||
castPlayer.stop()
|
||||
}
|
||||
|
||||
override fun onCastSessionAvailable() {
|
||||
startCastPlayer()
|
||||
}
|
||||
|
||||
override fun onCastSessionUnavailable() {
|
||||
startExoPlayer()
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class ExtendedTimeBar(
|
||||
context: Context,
|
||||
|
|
|
@ -174,12 +174,11 @@
|
|||
app:srcCompat="@drawable/ic_round_screen_rotation_alt_24"
|
||||
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
|
||||
|
||||
<ImageButton
|
||||
<androidx.mediarouter.app.MediaRouteButton
|
||||
android:id="@+id/exo_cast"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="48dp"
|
||||
android:backgroundTint="#00FFFFFF"
|
||||
android:src="@drawable/ic_round_cast_24"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="NavBarText">
|
||||
<item name="android:textAllCaps">true</item>
|
||||
<item name="android:fontFamily">@font/poppins_bold</item>
|
||||
|
@ -14,7 +15,6 @@
|
|||
</style>
|
||||
|
||||
|
||||
|
||||
<style name="Suffix">
|
||||
<item name="android:height">54dp</item>
|
||||
<item name="android:fontFamily">@font/poppins_bold</item>
|
||||
|
@ -37,15 +37,18 @@
|
|||
<item name="background">@id/bg</item>
|
||||
<item name="cornerFamily">rounded</item>
|
||||
</style>
|
||||
|
||||
<style name="roundedImageView" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">16dp</item>
|
||||
</style>
|
||||
|
||||
<style name="MySnackbar" parent="Widget.MaterialComponents.Snackbar">
|
||||
<item name="android:layout_marginLeft">16dp</item>
|
||||
<item name="android:layout_marginRight">16dp</item>
|
||||
<item name="android:background">@drawable/round_corner</item>
|
||||
</style>
|
||||
|
||||
<string name="MySnackBarText" parent="Widget.MaterialComponents.Snackbar.TextView" translatable="false">
|
||||
<item name="android:textColor">@color/bg_opp</item>
|
||||
</string>
|
||||
|
@ -67,10 +70,12 @@
|
|||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
</style>
|
||||
</style>
|
||||
|
||||
<style name="BottomNavBar" parent="">
|
||||
<!-- set background color to transparent -->
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="ThemeOverlay_Dantotsu_MediaRouter" parent="ThemeOverlay.AppCompat">
|
||||
</style>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue