native casting support

This commit is contained in:
Finnley Somdahl 2023-12-27 08:58:36 -06:00
parent bf908c5e37
commit e83a580486
6 changed files with 132 additions and 19 deletions

View file

@ -90,6 +90,9 @@ dependencies {
implementation "androidx.media3:media3-exoplayer-dash:$exo_version" implementation "androidx.media3:media3-exoplayer-dash:$exo_version"
implementation "androidx.media3:media3-datasource-okhttp:$exo_version" implementation "androidx.media3:media3-datasource-okhttp:$exo_version"
implementation "androidx.media3:media3-session:$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 // UI
implementation 'com.google.android.material:material:1.10.0' implementation 'com.google.android.material:material:1.10.0'

View file

@ -10,6 +10,8 @@
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <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.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@ -299,6 +301,9 @@
android:name=".connections.discord.DiscordService" android:name=".connections.discord.DiscordService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
</application> </application>
</manifest> </manifest>

View file

@ -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)
}

View file

@ -14,7 +14,6 @@ import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.hardware.Sensor
import android.hardware.SensorManager import android.hardware.SensorManager
import android.media.AudioManager import android.media.AudioManager
import android.media.AudioManager.* import android.media.AudioManager.*
@ -96,11 +95,15 @@ import java.util.concurrent.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt 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 @UnstableApi
@SuppressLint("SetTextI18n", "ClickableViewAccessibility") @SuppressLint("SetTextI18n", "ClickableViewAccessibility")
class ExoplayerView : AppCompatActivity(), Player.Listener { class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityListener {
private val resumeWindow = "resumeWindow" private val resumeWindow = "resumeWindow"
private val resumePosition = "resumePosition" private val resumePosition = "resumePosition"
@ -108,6 +111,8 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
private val playerOnPlay = "playerOnPlay" private val playerOnPlay = "playerOnPlay"
private lateinit var exoPlayer: ExoPlayer private lateinit var exoPlayer: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var castContext: CastContext
private lateinit var trackSelector: DefaultTrackSelector private lateinit var trackSelector: DefaultTrackSelector
private lateinit var cacheFactory: CacheDataSource.Factory private lateinit var cacheFactory: CacheDataSource.Factory
private lateinit var playbackParameters: PlaybackParameters private lateinit var playbackParameters: PlaybackParameters
@ -328,6 +333,11 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
setContentView(binding.root) setContentView(binding.root)
//Initialize //Initialize
castContext = CastContext.getSharedInstance(this)
castPlayer = CastPlayer(castContext)
castPlayer.setSessionAvailabilityListener(this)
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
hideSystemBars() hideSystemBars()
@ -387,7 +397,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
orientationListener = orientationListener =
object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) { object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) { override fun onOrientationChanged(orientation: Int) {
println(orientation)
if (orientation in 45..135) { if (orientation in 45..135) {
if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility = if (rotation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) exoRotate.visibility =
View.VISIBLE View.VISIBLE
@ -466,12 +475,18 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
if (isInitialized) { if (isInitialized) {
isPlayerPlaying = exoPlayer.isPlaying isPlayerPlaying = exoPlayer.isPlaying
(exoPlay.drawable as Animatable?)?.start() (exoPlay.drawable as Animatable?)?.start()
if (isPlayerPlaying) { if (isPlayerPlaying || castPlayer.isPlaying ) {
Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay) Glide.with(this).load(R.drawable.anim_play_to_pause).into(exoPlay)
exoPlayer.pause() exoPlayer.pause()
castPlayer.pause()
} else { } else {
Glide.with(this).load(R.drawable.anim_pause_to_play).into(exoPlay) if (!castPlayer.isPlaying && castPlayer.currentMediaItem != null) {
exoPlayer.play() 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 //Cast
if (settings.cast) { if (settings.cast) {
playerView.findViewById<ImageButton>(R.id.exo_cast).apply { playerView.findViewById<MediaRouteButton>(R.id.exo_cast).apply {
visibility = View.VISIBLE visibility = View.VISIBLE
setSafeOnClickListener { CastButtonFactory.setUpMediaRouteButton(context, this)
cast() dialogFactory = CustomCastThemeFactory()
}
} }
} }
@ -1483,7 +1497,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
super.onPause() super.onPause()
orientationListener?.disable() orientationListener?.disable()
if (isInitialized) { if (isInitialized) {
playerView.player?.pause() if (!castPlayer.isPlaying) {
playerView.player?.pause()
}
saveData( saveData(
"${media.id}_${media.anime!!.selectedEpisode}", "${media.id}_${media.anime!!.selectedEpisode}",
exoPlayer.currentPosition, exoPlayer.currentPosition,
@ -1504,7 +1520,9 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
} }
override fun onStop() { override fun onStop() {
playerView.player?.pause() if (!castPlayer.isPlaying) {
playerView.player?.pause()
}
super.onStop() super.onStop()
} }
@ -1797,7 +1815,6 @@ class ExoplayerView : AppCompatActivity(), Player.Listener {
// Enter PiP Mode // Enter PiP Mode
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@RequiresApi(Build.VERSION_CODES.N)
private fun enterPipMode() { private fun enterPipMode() {
wasPlaying = isPlayerPlaying wasPlaying = isPlayerPlaying
if (!pipEnabled) return 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") @SuppressLint("ViewConstructor")
class ExtendedTimeBar( class ExtendedTimeBar(
context: Context, context: Context,

View file

@ -174,12 +174,11 @@
app:srcCompat="@drawable/ic_round_screen_rotation_alt_24" app:srcCompat="@drawable/ic_round_screen_rotation_alt_24"
tools:ignore="ContentDescription,SpeakableTextPresentCheck" /> tools:ignore="ContentDescription,SpeakableTextPresentCheck" />
<ImageButton <androidx.mediarouter.app.MediaRouteButton
android:id="@+id/exo_cast" android:id="@+id/exo_cast"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="48dp" android:layout_height="48dp"
android:backgroundTint="#00FFFFFF" android:backgroundTint="#00FFFFFF"
android:src="@drawable/ic_round_cast_24"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="NavBarText"> <style name="NavBarText">
<item name="android:textAllCaps">true</item> <item name="android:textAllCaps">true</item>
<item name="android:fontFamily">@font/poppins_bold</item> <item name="android:fontFamily">@font/poppins_bold</item>
@ -14,7 +15,6 @@
</style> </style>
<style name="Suffix"> <style name="Suffix">
<item name="android:height">54dp</item> <item name="android:height">54dp</item>
<item name="android:fontFamily">@font/poppins_bold</item> <item name="android:fontFamily">@font/poppins_bold</item>
@ -37,19 +37,22 @@
<item name="background">@id/bg</item> <item name="background">@id/bg</item>
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
</style> </style>
<style name="roundedImageView" parent=""> <style name="roundedImageView" parent="">
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item> <item name="cornerSize">16dp</item>
</style> </style>
<style name="MySnackbar" parent="Widget.MaterialComponents.Snackbar"> <style name="MySnackbar" parent="Widget.MaterialComponents.Snackbar">
<item name="android:layout_marginLeft">16dp</item> <item name="android:layout_marginLeft">16dp</item>
<item name="android:layout_marginRight">16dp</item> <item name="android:layout_marginRight">16dp</item>
<item name="android:background">@drawable/round_corner</item> <item name="android:background">@drawable/round_corner</item>
</style> </style>
<string name="MySnackBarText" parent="Widget.MaterialComponents.Snackbar.TextView" translatable="false"> <string name="MySnackBarText" parent="Widget.MaterialComponents.Snackbar.TextView" translatable="false">
<item name="android:textColor">@color/bg_opp</item> <item name="android:textColor">@color/bg_opp</item>
</string> </string>
<style name="fontTooltip" parent="Widget.Material3.Tooltip"> <style name="fontTooltip" parent="Widget.Material3.Tooltip">
<item name="android:padding">8dp</item> <item name="android:padding">8dp</item>
<item name="android:textAppearance">@style/Suffix</item> <item name="android:textAppearance">@style/Suffix</item>
@ -67,10 +70,12 @@
<item name="android:windowIsFloating">true</item> <item name="android:windowIsFloating">true</item>
<item name="android:windowNoTitle">true</item> <item name="android:windowNoTitle">true</item>
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
</style> </style>
<style name="BottomNavBar" parent=""> <style name="BottomNavBar" parent="">
<!-- set background color to transparent --> <!-- set background color to transparent -->
<item name="android:background">@android:color/transparent</item> <item name="android:background">@android:color/transparent</item>
</style> </style>
<style name="ThemeOverlay_Dantotsu_MediaRouter" parent="ThemeOverlay.AppCompat">
</style>
</resources> </resources>