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