diff --git a/app/build.gradle b/app/build.gradle index 87966773e..ac0c11a05 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index 65e215af4..e61387df2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -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() } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 670e719ee..9e561215e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -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(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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 13050f02e..a6cbccd05 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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 }