From d734953b54e2506bbdf12c8164f0c94ccc82022f Mon Sep 17 00:00:00 2001 From: Hugh Daschbach Date: Sat, 27 Aug 2022 00:21:03 -0700 Subject: [PATCH] Replace deprecated SimpleExoPlayer with ExoPlayer. This is part of an effort to resolve deprecation warnings. Most of this is simple refactoring of interfaces that change between the two Player implementations. There are a few other changes that deserve further explanation. Testing indicated that the play/pause button was being reset to pause in MainActivity:refreshCurrentTrack. In the past this was likely masked by the ordering of other callbacks. We have removed the nowPlayingToggle.icon update from MainActivity, leaving that UI update to PlayerService. One of the bigger refactorings in PlayerService was forced by the deprecation of Player.EventListener.onPlayerStateChanged. That forced separation of handling playWhenReady and playbackState transitions. In the SimpleExoPlayer implementations, where these transitions were combined, the module attempted to work out playing state from a combination of these two state variables. In addition to separating the reaction to these state changes, we have added a listener to onIsPlayingChanged, eliminating the need for some of the earlier logic in Player.EventListener.onPlayerStateChanged. This addition, along with the separation of state transition processing, seems to provide a simpler implementation. But it is, certainly, a possible source of bugs. --- app/build.gradle.kts | 10 +- .../funkwhale/ffa/activities/MainActivity.kt | 2 - .../funkwhale/ffa/playback/MediaSession.kt | 14 +-- .../funkwhale/ffa/playback/PinService.kt | 6 +- .../funkwhale/ffa/playback/PlayerService.kt | 105 +++++++++--------- .../funkwhale/ffa/playback/QueueManager.kt | 15 ++- 6 files changed, 76 insertions(+), 76 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f93b57f..2ff9843 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -167,18 +167,18 @@ dependencies { implementation("com.google.android.material:material:1.6.1") implementation("com.android.support.constraint:constraint-layout:2.0.4") - implementation("com.google.android.exoplayer:exoplayer-core:2.14.2") - implementation("com.google.android.exoplayer:exoplayer-ui:2.14.2") - implementation("com.google.android.exoplayer:extension-mediasession:2.14.2") + implementation("com.google.android.exoplayer:exoplayer-core:2.18.1") + implementation("com.google.android.exoplayer:exoplayer-ui:2.18.1") + implementation("com.google.android.exoplayer:extension-mediasession:2.18.1") implementation("io.insert-koin:koin-core:3.1.2") implementation("io.insert-koin:koin-android:3.1.2") testImplementation("io.insert-koin:koin-test:3.1.2") - implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:2.14.0") { + implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-opus:789a4f83169cff5c7a91655bb828fde2cfde671a") { isTransitive = false } - implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:2.14.0") { + implementation("com.github.PaulWoitaschek.ExoPlayer-Extensions:extension-flac:789a4f83169cff5c7a91655bb828fde2cfde671a") { isTransitive = false } diff --git a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt index b3e2d68..efe6eb6 100644 --- a/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt +++ b/app/src/main/java/audio/funkwhale/ffa/activities/MainActivity.kt @@ -473,11 +473,9 @@ class MainActivity : AppCompatActivity() { binding.nowPlayingContainer?.nowPlayingTitle?.text = track.title binding.nowPlayingContainer?.nowPlayingAlbum?.text = track.artist.name - binding.nowPlayingContainer?.nowPlayingToggle?.icon = getDrawable(R.drawable.pause) binding.nowPlayingContainer?.nowPlayingDetailsTitle?.text = track.title binding.nowPlayingContainer?.nowPlayingDetailsArtist?.text = track.artist.name - binding.nowPlayingContainer?.nowPlayingDetailsToggle?.icon = getDrawable(R.drawable.pause) Picasso.get() .maybeLoad(maybeNormalizeUrl(track.album?.cover?.urls?.original)) diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt b/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt index 4747a2e..ce3d88c 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/MediaSession.kt @@ -41,7 +41,7 @@ class MediaSession(private val context: Context) { MediaSessionConnector(session).also { it.setQueueNavigator(FFAQueueNavigator()) - it.setMediaButtonEventHandler { _, _, intent -> + it.setMediaButtonEventHandler { _, intent -> if (!active) { Intent(context, PlayerService::class.java).let { player -> player.action = intent.action @@ -65,13 +65,11 @@ class MediaSession(private val context: Context) { } class FFAQueueNavigator : MediaSessionConnector.QueueNavigator { - override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) { + override fun onSkipToQueueItem(player: Player, id: Long) { CommandBus.send(Command.PlayTrack(id.toInt())) } - override fun onCurrentWindowIndexChanged(player: Player) {} - - override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true + override fun onCommand(player: Player, command: String, extras: Bundle?, cb: ResultReceiver?) = true override fun getSupportedQueueNavigatorActions(player: Player): Long { return PlaybackStateCompat.ACTION_PLAY_PAUSE or @@ -80,13 +78,13 @@ class FFAQueueNavigator : MediaSessionConnector.QueueNavigator { PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM } - override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) { + override fun onSkipToNext(player: Player) { CommandBus.send(Command.NextTrack) } - override fun getActiveQueueItemId(player: Player?) = player?.currentWindowIndex?.toLong() ?: 0 + override fun getActiveQueueItemId(player: Player?) = player?.currentMediaItemIndex?.toLong() ?: 0 - override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) { + override fun onSkipToPrevious(player: Player) { CommandBus.send(Command.PreviousTrack) } diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt index f3bd1ce..321c125 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/PinService.kt @@ -80,14 +80,16 @@ class PinService : DownloadService(AppContext.NOTIFICATION_DOWNLOADS) { override fun getScheduler(): Scheduler? = null - override fun getForegroundNotification(downloads: MutableList): Notification { + override fun getForegroundNotification(downloads: MutableList, + notMetRequirements: Int): Notification { val description = resources.getQuantityString(R.plurals.downloads_description, downloads.size, downloads.size) return DownloadNotificationHelper( this, AppContext.NOTIFICATION_CHANNEL_DOWNLOADS - ).buildProgressNotification(this, R.drawable.downloads, null, description, downloads) + ).buildProgressNotification(this, R.drawable.downloads, null, description, + downloads, notMetRequirements) } private fun getDownloads() = downloadManager.downloadIndex.getDownloads() diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt index 9c74da5..68adff5 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/PlayerService.kt @@ -31,11 +31,10 @@ import audio.funkwhale.ffa.utils.log import audio.funkwhale.ffa.utils.maybeNormalizeUrl import audio.funkwhale.ffa.utils.onApi import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.ExoPlaybackException +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.PlaybackException import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.SimpleExoPlayer -import com.google.android.exoplayer2.source.TrackGroupArray -import com.google.android.exoplayer2.trackselection.TrackSelectionArray +import com.google.android.exoplayer2.Tracks import com.squareup.picasso.Picasso import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.IO @@ -65,7 +64,7 @@ class PlayerService : Service() { private lateinit var queue: QueueManager private lateinit var mediaControlsManager: MediaControlsManager - private lateinit var player: SimpleExoPlayer + private lateinit var player: ExoPlayer private val mediaMetadataBuilder = MediaMetadataCompat.Builder() @@ -132,12 +131,13 @@ class PlayerService : Service() { mediaControlsManager = MediaControlsManager(this, scope, mediaSession.session) - player = SimpleExoPlayer.Builder(this).build().apply { + player = ExoPlayer.Builder(this).build().apply { playWhenReady = false playerEventListener = PlayerEventListener().also { addListener(it) } + EventBus.send(Event.StateChanged(this.isPlaying())) } mediaSession.active = true @@ -151,7 +151,8 @@ class PlayerService : Service() { } if (queue.current > -1) { - player.prepare(queue.dataSources) + player.setMediaSource(queue.dataSources) + player.prepare() FFACache.getLine(this, "progress")?.let { player.seekTo(queue.current, it.toLong()) @@ -180,7 +181,8 @@ class PlayerService : Service() { if (!command.fromRadio) radioPlayer.stop() queue.replace(command.queue) - player.prepare(queue.dataSources, true, true) + player.setMediaSource(queue.dataSources) + player.prepare() setPlaybackState(true) @@ -307,7 +309,8 @@ class PlayerService : Service() { } if (state && player.playbackState == Player.STATE_IDLE) { - player.prepare(queue.dataSources) + player.setMediaSource(queue.dataSources) + player.prepare() } if (hasAudioFocus(state)) { @@ -318,7 +321,7 @@ class PlayerService : Service() { } private fun togglePlayback() { - setPlaybackState(!player.playWhenReady) + setPlaybackState(!player.isPlaying) } private fun skipToPreviousTrack() { @@ -326,11 +329,11 @@ class PlayerService : Service() { return player.seekTo(0) } - player.previous() + player.seekToPrevious() } private fun skipToNextTrack() { - player.next() + player.seekToNext() FFACache.set(this@PlayerService, "progress", "0") ProgressBus.send(0, 0, 0) @@ -419,9 +422,14 @@ class PlayerService : Service() { } @SuppressLint("NewApi") - inner class PlayerEventListener : Player.EventListener { - override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { - super.onPlayerStateChanged(playWhenReady, playbackState) + inner class PlayerEventListener : Player.Listener { + override fun onIsPlayingChanged(isPlaying: Boolean) { + super.onIsPlayingChanged(isPlaying) + mediaControlsManager.updateNotification(queue.current(), isPlaying) + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + super.onPlayWhenReadyChanged(playWhenReady, reason) EventBus.send(Event.StateChanged(playWhenReady)) @@ -429,55 +437,45 @@ class PlayerService : Service() { CommandBus.send(Command.RefreshTrack(queue.current())) } - when (playWhenReady) { - true -> { - when (playbackState) { - Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true) - Player.STATE_BUFFERING -> EventBus.send(Event.Buffering(true)) - Player.STATE_ENDED -> { - setPlaybackState(false) + if (!playWhenReady) { + Build.VERSION_CODES.N.onApi( + { stopForeground(STOP_FOREGROUND_DETACH) }, + { stopForeground(false) } + ) + } + } - queue.current = 0 - player.seekTo(0, C.TIME_UNSET) + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + EventBus.send(Event.Buffering(playbackState == Player.STATE_BUFFERING)) + when (playbackState) { + Player.STATE_ENDED -> { + setPlaybackState(false) - ProgressBus.send(0, 0, 0) - } + queue.current = 0 + player.seekTo(0, C.TIME_UNSET) - Player.STATE_IDLE -> { - setPlaybackState(false) - - return EventBus.send(Event.PlaybackStopped) - } - } - - if (playbackState != Player.STATE_BUFFERING) EventBus.send(Event.Buffering(false)) + ProgressBus.send(0, 0, 0) } - false -> { - EventBus.send(Event.Buffering(false)) + Player.STATE_IDLE -> { + setPlaybackState(false) - Build.VERSION_CODES.N.onApi( - { stopForeground(STOP_FOREGROUND_DETACH) }, - { stopForeground(false) } - ) + EventBus.send(Event.PlaybackStopped) - when (playbackState) { - Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), false) - Player.STATE_IDLE -> mediaControlsManager.remove() + if (!player.playWhenReady) { + mediaControlsManager.remove() } } } } - override fun onTracksChanged( - trackGroups: TrackGroupArray, - trackSelections: TrackSelectionArray - ) { - super.onTracksChanged(trackGroups, trackSelections) + override fun onTracksChanged(tracks: Tracks) { + super.onTracksChanged(tracks) - if (queue.current != player.currentWindowIndex) { - queue.current = player.currentWindowIndex - mediaControlsManager.updateNotification(queue.current(), player.playWhenReady) + if (queue.current != player.currentMediaItemIndex) { + queue.current = player.currentMediaItemIndex + mediaControlsManager.updateNotification(queue.current(), player.isPlaying) } if (queue.get().isNotEmpty() && @@ -510,13 +508,14 @@ class PlayerService : Service() { } } - override fun onPlayerError(error: ExoPlaybackException) { + override fun onPlayerError(error: PlaybackException) { EventBus.send(Event.PlaybackError(getString(R.string.error_playback))) if (player.playWhenReady) { queue.current++ - player.prepare(queue.dataSources, true, true) + player.setMediaSource(queue.dataSources, true) player.seekTo(queue.current, 0) + player.prepare() CommandBus.send(Command.RefreshTrack(queue.current())) } diff --git a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt index fe9bf31..821b860 100644 --- a/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt +++ b/app/src/main/java/audio/funkwhale/ffa/playback/QueueManager.kt @@ -12,6 +12,7 @@ import audio.funkwhale.ffa.utils.FFACache import audio.funkwhale.ffa.utils.log import audio.funkwhale.ffa.utils.mustNormalizeUrl import com.github.kittinunf.fuel.gson.gsonDeserializerOf +import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.source.ConcatenatingMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.gson.Gson @@ -38,8 +39,8 @@ class QueueManager(val context: Context) { metadata.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") - ProgressiveMediaSource.Factory(factory).setTag(track.title) - .createMediaSource(Uri.parse(url)) + val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build() + ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem) } ) } @@ -63,8 +64,8 @@ class QueueManager(val context: Context) { val factory = cacheDataSourceFactoryProvider.create(context) val sources = tracks.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") - - ProgressiveMediaSource.Factory(factory).setTag(track.title).createMediaSource(Uri.parse(url)) + val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build() + ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem) } metadata = tracks.toMutableList() @@ -84,7 +85,8 @@ class QueueManager(val context: Context) { val sources = missingTracks.map { track -> val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") - ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)) + val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build() + ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem) } metadata.addAll(tracks) @@ -101,7 +103,8 @@ class QueueManager(val context: Context) { val url = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "") if (metadata.indexOf(track) == -1) { - ProgressiveMediaSource.Factory(factory).createMediaSource(Uri.parse(url)).let { + val mediaItem = MediaItem.fromUri(Uri.parse(url)).buildUpon().setTag(track.title).build() + ProgressiveMediaSource.Factory(factory).createMediaSource(mediaItem).let { dataSources.addMediaSource(current + 1, it) metadata.add(current + 1, track) }