Add external media controls
Add the ability to listen to external media controls, such as through a bluetooth headset.
This commit is contained in:
parent
3251b84e23
commit
2ded706445
4 changed files with 57 additions and 12 deletions
|
@ -69,6 +69,9 @@ dependencies {
|
|||
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
|
||||
|
||||
// Media
|
||||
implementation 'androidx.media:media:1.2.0'
|
||||
|
||||
// --- THIRD PARTY ---
|
||||
|
||||
// Image loading
|
||||
|
@ -81,7 +84,9 @@ dependencies {
|
|||
ktlint "com.pinterest:ktlint:0.37.2"
|
||||
|
||||
// ExoPlayer
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.12.1'
|
||||
def exoplayer_version = "2.12.1"
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
||||
implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
|
||||
|
||||
// Memory Leak checking
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
|
||||
|
|
|
@ -25,6 +25,8 @@ class CompactPlaybackFragment : Fragment() {
|
|||
): View? {
|
||||
val binding = FragmentCompactPlaybackBinding.inflate(inflater)
|
||||
|
||||
// FIXME: Prevent the play/pause icon from animating on startup
|
||||
// [requires new callback from PlaybackStateManager]
|
||||
val iconPauseToPlay = ContextCompat.getDrawable(
|
||||
requireContext(), R.drawable.ic_pause_to_play
|
||||
) as AnimatedVectorDrawable
|
||||
|
@ -60,7 +62,7 @@ class CompactPlaybackFragment : Fragment() {
|
|||
}
|
||||
|
||||
playbackModel.isPlaying.observe(viewLifecycleOwner) {
|
||||
if (playbackModel.canAnimate) {
|
||||
if (true) {
|
||||
if (it) {
|
||||
// Animate the icon transition when the playing status switches
|
||||
binding.playbackControls.setImageDrawable(iconPauseToPlay)
|
||||
|
@ -90,6 +92,6 @@ class CompactPlaybackFragment : Fragment() {
|
|||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
playbackModel.resetAnimStatus()
|
||||
// playbackModel.resetAnimStatus()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,19 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Parcelable
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
|
@ -44,6 +49,14 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
}
|
||||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
private val buttonMediaCallback = object : MediaSessionCompat.Callback() {
|
||||
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
|
||||
Log.d(this::class.simpleName, "Hello?")
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private val serviceJob = Job()
|
||||
private val serviceScope = CoroutineScope(
|
||||
|
@ -67,6 +80,39 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
mediaSession = MediaSessionCompat(this, packageName).apply {
|
||||
isActive = true
|
||||
}
|
||||
|
||||
val connector = MediaSessionConnector(mediaSession)
|
||||
connector.setPlayer(player)
|
||||
connector.setMediaButtonEventHandler { _, _, mediaButtonEvent ->
|
||||
val item = mediaButtonEvent
|
||||
.getParcelableExtra<Parcelable>(Intent.EXTRA_KEY_EVENT) as KeyEvent
|
||||
|
||||
if (item.action == KeyEvent.ACTION_DOWN) {
|
||||
when (item.keyCode) {
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT -> {
|
||||
playbackManager.next()
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
|
||||
playbackManager.prev()
|
||||
}
|
||||
|
||||
// TODO: Implement the other callbacks for
|
||||
// CLOSE/STOP & REWIND
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
notification = createNotification()
|
||||
|
||||
playbackManager.addCallback(this)
|
||||
|
@ -76,6 +122,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
|||
super.onDestroy()
|
||||
|
||||
player.release()
|
||||
mediaSession.release()
|
||||
serviceJob.cancel()
|
||||
playbackManager.removeCallback(this)
|
||||
|
||||
|
|
|
@ -56,9 +56,6 @@ class PlaybackViewModel() : ViewModel(), PlaybackStateCallback {
|
|||
it.slice((mIndex.value!! + 1) until it.size)
|
||||
}
|
||||
|
||||
private var mCanAnimate = false
|
||||
val canAnimate: Boolean get() = mCanAnimate
|
||||
|
||||
// Service setup
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
||||
|
@ -168,8 +165,6 @@ class PlaybackViewModel() : ViewModel(), PlaybackStateCallback {
|
|||
|
||||
// Flip the playing status.
|
||||
fun invertPlayingStatus() {
|
||||
mCanAnimate = true
|
||||
|
||||
playbackManager.setPlayingStatus(!playbackManager.isPlaying)
|
||||
}
|
||||
|
||||
|
@ -180,10 +175,6 @@ class PlaybackViewModel() : ViewModel(), PlaybackStateCallback {
|
|||
|
||||
// --- OTHER FUNCTIONS ---
|
||||
|
||||
fun resetAnimStatus() {
|
||||
mCanAnimate = false
|
||||
}
|
||||
|
||||
fun setSeekingStatus(value: Boolean) {
|
||||
mIsSeeking.value = value
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue