mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-22 21:02:16 +03:00
feat(player): handle media buttons from earphones, bluetooth devices and possibly android tv remotes
This commit is contained in:
parent
04aad934d8
commit
ab2b6da9b2
3 changed files with 107 additions and 2 deletions
|
@ -187,6 +187,7 @@ dependencies {
|
|||
implementation(androidx.recyclerview)
|
||||
implementation(androidx.viewpager)
|
||||
implementation(androidx.profileinstaller)
|
||||
implementation(androidx.mediasession)
|
||||
|
||||
implementation(androidx.bundles.lifecycle)
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ import android.os.Bundle
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.support.v4.media.session.MediaControllerCompat
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
@ -146,6 +149,10 @@ class PlayerActivity : BaseActivity() {
|
|||
|
||||
lateinit var binding: PlayerActivityBinding
|
||||
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
|
||||
private val playbackStateBuilder = PlaybackStateCompat.Builder()
|
||||
|
||||
internal val player get() = binding.player
|
||||
|
||||
internal val playerControls get() = binding.playerControls
|
||||
|
@ -276,6 +283,7 @@ class PlayerActivity : BaseActivity() {
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
setupPlayerControls()
|
||||
setupMediaSession()
|
||||
setupPlayerMPV()
|
||||
setupPlayerAudio()
|
||||
setupPlayerBrightness()
|
||||
|
@ -433,6 +441,89 @@ class PlayerActivity : BaseActivity() {
|
|||
verticalScrollLeft(0F)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun setupMediaSession() {
|
||||
mediaSession = MediaSessionCompat(this, "Aniyomi_Player_Session").apply {
|
||||
// Enable callbacks from MediaButtons and TransportControls
|
||||
setFlags(
|
||||
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
|
||||
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS,
|
||||
)
|
||||
|
||||
// Do not let MediaButtons restart the player when the app is not visible
|
||||
setMediaButtonReceiver(null)
|
||||
|
||||
setPlaybackState(
|
||||
with(playbackStateBuilder) {
|
||||
setState(
|
||||
PlaybackStateCompat.STATE_NONE,
|
||||
0L,
|
||||
0.0F,
|
||||
)
|
||||
build()
|
||||
},
|
||||
)
|
||||
|
||||
// Implement methods that handle callbacks from a media controller
|
||||
setCallback(
|
||||
object : MediaSessionCompat.Callback() {
|
||||
override fun onPlay() {
|
||||
player.paused = false
|
||||
playerControls.toggleControls(isTapped = true)
|
||||
updatePlaybackState()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
player.paused = true
|
||||
playerControls.toggleControls()
|
||||
updatePlaybackState(pause = true)
|
||||
}
|
||||
|
||||
override fun onSkipToPrevious() {
|
||||
changeEpisode(viewModel.getAdjacentEpisodeId(previous = true))
|
||||
}
|
||||
|
||||
override fun onSkipToNext() {
|
||||
changeEpisode(viewModel.getAdjacentEpisodeId(previous = false))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
MediaControllerCompat(this, mediaSession).also { mediaController ->
|
||||
MediaControllerCompat.setMediaController(this, mediaController)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePlaybackState(cachePause: Boolean = false, pause: Boolean = false) {
|
||||
val state = when {
|
||||
player.timePos?.let { it < 0 } ?: true ||
|
||||
player.duration?.let { it <= 0 } ?: true -> PlaybackStateCompat.STATE_CONNECTING
|
||||
cachePause -> PlaybackStateCompat.STATE_BUFFERING
|
||||
pause or (player.paused == true) -> PlaybackStateCompat.STATE_PAUSED
|
||||
else -> PlaybackStateCompat.STATE_PLAYING
|
||||
}
|
||||
var actions = PlaybackStateCompat.ACTION_PLAY or
|
||||
PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
||||
PlaybackStateCompat.ACTION_PAUSE
|
||||
if (viewModel.currentPlaylist.size > 1) {
|
||||
actions = actions or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
|
||||
}
|
||||
|
||||
mediaSession.setPlaybackState(
|
||||
with(playbackStateBuilder) {
|
||||
setState(
|
||||
state,
|
||||
player.timePos?.toLong() ?: 0L,
|
||||
player.playbackSpeed?.toFloat() ?: 1.0f,
|
||||
)
|
||||
setActions(actions)
|
||||
build()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun loadDeviceDimensions() {
|
||||
this@PlayerActivity.requestedOrientation = playerPreferences.defaultPlayerOrientationType().get()
|
||||
|
@ -497,6 +588,9 @@ class PlayerActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mediaSession.isActive = false
|
||||
mediaSession.release()
|
||||
|
||||
playerPreferences.playerVolumeValue().set(fineVolume)
|
||||
playerPreferences.playerBrightnessValue().set(brightness)
|
||||
MPVLib.removeLogObserver(playerObserver)
|
||||
|
@ -1560,19 +1654,28 @@ class PlayerActivity : BaseActivity() {
|
|||
"time-pos" -> {
|
||||
playerControls.updatePlaybackPos(value.toInt())
|
||||
viewModel.viewModelScope.launchUI { aniSkipStuff(value) }
|
||||
updatePlaybackState()
|
||||
}
|
||||
"duration" -> {
|
||||
playerControls.updatePlaybackDuration(value.toInt())
|
||||
mediaSession.isActive = true
|
||||
updatePlaybackState()
|
||||
}
|
||||
"duration" -> playerControls.updatePlaybackDuration(value.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
internal fun eventPropertyUi(property: String, value: Boolean) {
|
||||
when (property) {
|
||||
"seeking" -> isSeeking(value)
|
||||
"paused-for-cache" -> showLoadingIndicator(value)
|
||||
"paused-for-cache" -> {
|
||||
showLoadingIndicator(value)
|
||||
updatePlaybackState(cachePause = true)
|
||||
}
|
||||
"pause" -> {
|
||||
if (!isFinishing) {
|
||||
setAudioFocus(value)
|
||||
updatePlaybackStatus(value)
|
||||
updatePlaybackState(pause = true)
|
||||
}
|
||||
}
|
||||
"eof-reached" -> endFile(value)
|
||||
|
|
|
@ -16,6 +16,7 @@ recyclerview = "androidx.recyclerview:recyclerview:1.3.0"
|
|||
viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01"
|
||||
glance = "androidx.glance:glance-appwidget:1.0.0-alpha03"
|
||||
profileinstaller = "androidx.profileinstaller:profileinstaller:1.3.0"
|
||||
mediasession = "androidx.media:media:1.6.0"
|
||||
|
||||
lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" }
|
||||
lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" }
|
||||
|
|
Loading…
Reference in a new issue