diff --git a/app/build.gradle b/app/build.gradle
index 0f0292895f1..89adda862f9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cd7cec878a3..7750a9586ff 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,8 @@
android:required="false" />
+
@@ -299,6 +301,9 @@
android:name=".connections.discord.DiscordService"
android:exported="false"
android:foregroundServiceType="dataSync" />
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt b/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt
new file mode 100644
index 00000000000..850072b8206
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/media/anime/CustomCastThemeFactory.kt
@@ -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)
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
index c7cd6426365..253080e24b4 100644
--- a/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
+++ b/app/src/main/java/ani/dantotsu/media/anime/ExoplayerView.kt
@@ -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,12 +475,18 @@ 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 {
- Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay)
- exoPlayer.play()
+ 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()
+ }
}
}
}
@@ -1074,11 +1089,10 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
//Cast
if (settings.cast) {
- playerView.findViewById(R.id.exo_cast).apply {
+ playerView.findViewById(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) {
- playerView.player?.pause()
+ 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() {
- playerView.player?.pause()
+ 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,
diff --git a/app/src/main/res/layout/exo_player_control_view.xml b/app/src/main/res/layout/exo_player_control_view.xml
index eb4d99ea279..ceef7edc791 100644
--- a/app/src/main/res/layout/exo_player_control_view.xml
+++ b/app/src/main/res/layout/exo_player_control_view.xml
@@ -174,12 +174,11 @@
app:srcCompat="@drawable/ic_round_screen_rotation_alt_24"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
-
+
-
+
+
+
- @color/bg_opp
-
+
+
+
\ No newline at end of file