From ce05acad2173947c2fde691d0ae6ffa477ddeb82 Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Mon, 1 Jun 2020 16:31:58 +0200 Subject: [PATCH] Send track played reports to Funkwhale whenever a track finishes playing. Closes #40. --- .../apognu/otter/activities/MainActivity.kt | 280 ++++++++++-------- .../apognu/otter/playback/PlayerService.kt | 110 ++----- .../apognu/otter/repositories/HttpUpstream.kt | 2 - .../com/github/apognu/otter/utils/EventBus.kt | 2 + 4 files changed, 177 insertions(+), 217 deletions(-) diff --git a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt index 63d5022..b81a545 100644 --- a/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt +++ b/app/src/main/java/com/github/apognu/otter/activities/MainActivity.kt @@ -27,7 +27,10 @@ import com.github.apognu.otter.repositories.FavoritedRepository import com.github.apognu.otter.repositories.FavoritesRepository import com.github.apognu.otter.repositories.Repository import com.github.apognu.otter.utils.* +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.coroutines.awaitStringResponse import com.google.android.exoplayer2.Player +import com.google.gson.Gson import com.preference.PowerPreference import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.activity_main.* @@ -236,136 +239,9 @@ class MainActivity : AppCompatActivity() { } } - is Event.TrackPlayed -> { - message.track?.let { track -> - if (now_playing.visibility == View.GONE) { - now_playing.visibility = View.VISIBLE - now_playing.alpha = 0f - - now_playing.animate() - .alpha(1.0f) - .setDuration(400) - .setListener(null) - .start() - - (container.layoutParams as? ViewGroup.MarginLayoutParams)?.let { - it.bottomMargin = it.bottomMargin * 2 - } - - landscape_queue?.let { landscape_queue -> - (landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let { - it.bottomMargin = it.bottomMargin * 2 - } - } - } - - now_playing_title.text = track.title - now_playing_album.text = track.artist.name - now_playing_toggle.icon = getDrawable(R.drawable.pause) - - now_playing_details_title.text = track.title - now_playing_details_artist.text = track.artist.name - now_playing_details_toggle.icon = getDrawable(R.drawable.pause) - - Picasso.get() - .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) - .fit() - .centerCrop() - .into(now_playing_cover) - - now_playing_details_cover?.let { now_playing_details_cover -> - Picasso.get() - .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) - .fit() - .centerCrop() - .into(now_playing_details_cover) - } - - if (now_playing_details_cover == null) { - GlobalScope.launch(IO) { - val width = DisplayMetrics().apply { - windowManager.defaultDisplay.getMetrics(this) - }.widthPixels - - val backgroundCover = Picasso.get() - .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) - .get() - .run { Bitmap.createScaledBitmap(this, width, width, false).toDrawable(resources) } - .apply { - alpha = 20 - gravity = Gravity.CENTER - } - - withContext(Main) { - now_playing_details.background = backgroundCover - } - } - } - - now_playing_details_repeat?.let { now_playing_details_repeat -> - changeRepeatMode(Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0) - - now_playing_details_repeat.setOnClickListener { - val current = Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0 - - changeRepeatMode((current + 1) % 3) - } - } - - now_playing_details_info?.let { now_playing_details_info -> - now_playing_details_info.setOnClickListener { - PopupMenu(this@MainActivity, now_playing_details_info, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { - inflate(R.menu.track_info) - - setOnMenuItemClickListener { - when (it.itemId) { - R.id.track_info_artist -> ArtistsFragment.openAlbums(this@MainActivity, track.artist, art = track.album.cover.original) - R.id.track_info_album -> AlbumsFragment.openTracks(this@MainActivity, track.album) - R.id.track_info_details -> TrackInfoDetailsFragment.new(track).show(supportFragmentManager, "dialog") - } - - now_playing.close() - - true - } - - show() - } - } - } - - now_playing_details_favorite?.let { now_playing_details_favorite -> - favoriteCheckRepository.fetch().untilNetwork(IO) { favorites, _, _ -> - GlobalScope.launch(Main) { - track.favorite = favorites.contains(track.id) - - when (track.favorite) { - true -> now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) - false -> now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) - } - } - } - - now_playing_details_favorite.setOnClickListener { - when (track.favorite) { - true -> { - favoriteRepository.deleteFavorite(track.id) - now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) - } - - false -> { - favoriteRepository.addFavorite(track.id) - now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) - } - } - - track.favorite = !track.favorite - - favoriteRepository.fetch(Repository.Origin.Network.origin) - } - } - } - } + is Event.TrackPlayed -> refreshCurrentTrack(message.track) + is Event.RefreshTrack -> refreshCurrentTrack(message.track) + is Event.TrackFinished -> incrementListenCount(message.track) is Event.StateChanged -> { when (message.playing) { @@ -411,6 +287,137 @@ class MainActivity : AppCompatActivity() { } } + private fun refreshCurrentTrack(track: Track?) { + track?.let { track -> + if (now_playing.visibility == View.GONE) { + now_playing.visibility = View.VISIBLE + now_playing.alpha = 0f + + now_playing.animate() + .alpha(1.0f) + .setDuration(400) + .setListener(null) + .start() + + (container.layoutParams as? ViewGroup.MarginLayoutParams)?.let { + it.bottomMargin = it.bottomMargin * 2 + } + + landscape_queue?.let { landscape_queue -> + (landscape_queue.layoutParams as? ViewGroup.MarginLayoutParams)?.let { + it.bottomMargin = it.bottomMargin * 2 + } + } + } + + now_playing_title.text = track.title + now_playing_album.text = track.artist.name + now_playing_toggle.icon = getDrawable(R.drawable.pause) + + now_playing_details_title.text = track.title + now_playing_details_artist.text = track.artist.name + now_playing_details_toggle.icon = getDrawable(R.drawable.pause) + + Picasso.get() + .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) + .fit() + .centerCrop() + .into(now_playing_cover) + + now_playing_details_cover?.let { now_playing_details_cover -> + Picasso.get() + .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) + .fit() + .centerCrop() + .into(now_playing_details_cover) + } + + if (now_playing_details_cover == null) { + GlobalScope.launch(IO) { + val width = DisplayMetrics().apply { + windowManager.defaultDisplay.getMetrics(this) + }.widthPixels + + val backgroundCover = Picasso.get() + .maybeLoad(maybeNormalizeUrl(track.album.cover.original)) + .get() + .run { Bitmap.createScaledBitmap(this, width, width, false).toDrawable(resources) } + .apply { + alpha = 20 + gravity = Gravity.CENTER + } + + withContext(Main) { + now_playing_details.background = backgroundCover + } + } + } + + now_playing_details_repeat?.let { now_playing_details_repeat -> + changeRepeatMode(Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0) + + now_playing_details_repeat.setOnClickListener { + val current = Cache.get(this@MainActivity, "repeat")?.readLine()?.toInt() ?: 0 + + changeRepeatMode((current + 1) % 3) + } + } + + now_playing_details_info?.let { now_playing_details_info -> + now_playing_details_info.setOnClickListener { + PopupMenu(this@MainActivity, now_playing_details_info, Gravity.START, R.attr.actionOverflowMenuStyle, 0).apply { + inflate(R.menu.track_info) + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.track_info_artist -> ArtistsFragment.openAlbums(this@MainActivity, track.artist, art = track.album.cover.original) + R.id.track_info_album -> AlbumsFragment.openTracks(this@MainActivity, track.album) + R.id.track_info_details -> TrackInfoDetailsFragment.new(track).show(supportFragmentManager, "dialog") + } + + now_playing.close() + + true + } + + show() + } + } + } + + now_playing_details_favorite?.let { now_playing_details_favorite -> + favoriteCheckRepository.fetch().untilNetwork(IO) { favorites, _, _ -> + GlobalScope.launch(Main) { + track.favorite = favorites.contains(track.id) + + when (track.favorite) { + true -> now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) + false -> now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) + } + } + } + + now_playing_details_favorite.setOnClickListener { + when (track.favorite) { + true -> { + favoriteRepository.deleteFavorite(track.id) + now_playing_details_favorite.setColorFilter(getColor(R.color.controlForeground)) + } + + false -> { + favoriteRepository.addFavorite(track.id) + now_playing_details_favorite.setColorFilter(getColor(R.color.colorFavorite)) + } + } + + track.favorite = !track.favorite + + favoriteRepository.fetch(Repository.Origin.Network.origin) + } + } + } + } + private fun changeRepeatMode(index: Int) { when (index) { // From no repeat to repeat all @@ -446,4 +453,17 @@ class MainActivity : AppCompatActivity() { } } } + + private fun incrementListenCount(track: Track?) { + track?.let { track -> + GlobalScope.launch(IO) { + Fuel + .post(mustNormalizeUrl("/api/v1/history/listenings/")) + .authorize() + .header("Content-Type", "application/json") + .body(Gson().toJson(mapOf("track" to track.id))) + .awaitStringResponse() + } + } + } } diff --git a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt index f2b348b..02800bc 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt @@ -131,17 +131,8 @@ class PlayerService : Service() { EventBus.send(Event.QueueChanged) if (queue.metadata.isNotEmpty()) { - EventBus.send( - Event.TrackPlayed( - queue.current(), - player.playWhenReady - ) - ) - EventBus.send( - Event.StateChanged( - player.playWhenReady - ) - ) + EventBus.send(Event.RefreshTrack(queue.current(), player.playWhenReady)) + EventBus.send(Event.StateChanged(player.playWhenReady)) } } @@ -154,7 +145,7 @@ class PlayerService : Service() { state(true) EventBus.send( - Event.TrackPlayed( + Event.RefreshTrack( queue.current(), true ) @@ -172,12 +163,7 @@ class PlayerService : Service() { state(true) - EventBus.send( - Event.TrackPlayed( - queue.current(), - true - ) - ) + EventBus.send(Event.RefreshTrack(queue.current(),true)) } is Command.ToggleState -> toggle() @@ -211,21 +197,9 @@ class PlayerService : Service() { jobs.add(GlobalScope.launch(Main) { RequestBus.get().collect { request -> when (request) { - is Request.GetCurrentTrack -> request.channel?.offer( - Response.CurrentTrack( - queue.current() - ) - ) - is Request.GetState -> request.channel?.offer( - Response.State( - player.playWhenReady - ) - ) - is Request.GetQueue -> request.channel?.offer( - Response.Queue( - queue.get() - ) - ) + is Request.GetCurrentTrack -> request.channel?.offer(Response.CurrentTrack(queue.current())) + is Request.GetState -> request.channel?.offer(Response.State(player.playWhenReady)) + is Request.GetQueue -> request.channel?.offer(Response.Queue(queue.get())) } } }) @@ -285,11 +259,7 @@ class PlayerService : Service() { if (!state) { val (progress, _, _) = progress() - Cache.set( - this@PlayerService, - "progress", - progress.toString().toByteArray() - ) + Cache.set(this@PlayerService,"progress", progress.toString().toByteArray()) } if (state && player.playbackState == Player.STATE_IDLE) { @@ -365,52 +335,27 @@ class PlayerService : Service() { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { super.onPlayerStateChanged(playWhenReady, playbackState) - EventBus.send( - Event.StateChanged( - playWhenReady - ) - ) + EventBus.send(Event.StateChanged(playWhenReady)) if (queue.current == -1) { - EventBus.send( - Event.TrackPlayed( - queue.current(), - playWhenReady - ) - ) + EventBus.send(Event.TrackPlayed(queue.current(), playWhenReady)) } when (playWhenReady) { true -> { when (playbackState) { Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true) - Player.STATE_BUFFERING -> EventBus.send( - Event.Buffering( - true - ) - ) + Player.STATE_BUFFERING -> EventBus.send(Event.Buffering(true)) Player.STATE_IDLE -> state(false) Player.STATE_ENDED -> EventBus.send(Event.PlaybackStopped) } - if (playbackState != Player.STATE_BUFFERING) EventBus.send( - Event.Buffering( - false - ) - ) + if (playbackState != Player.STATE_BUFFERING) EventBus.send(Event.Buffering(false)) } false -> { - EventBus.send( - Event.StateChanged( - false - ) - ) - EventBus.send( - Event.Buffering( - false - ) - ) + EventBus.send(Event.StateChanged(false)) + EventBus.send(Event.Buffering(false)) if (playbackState == Player.STATE_READY) { mediaControlsManager.updateNotification(queue.current(), false) @@ -435,26 +380,21 @@ class PlayerService : Service() { } } - Cache.set( - this@PlayerService, - "current", - queue.current.toString().toByteArray() - ) + Cache.set(this@PlayerService,"current", queue.current.toString().toByteArray() ) - EventBus.send( - Event.TrackPlayed( - queue.current(), - true - ) - ) + EventBus.send(Event.RefreshTrack(queue.current(),true) ) + } + + override fun onPositionDiscontinuity(reason: Int) { + super.onPositionDiscontinuity(reason) + + if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION) { + EventBus.send(Event.TrackFinished(queue.current())) + } } override fun onPlayerError(error: ExoPlaybackException?) { - EventBus.send( - Event.PlaybackError( - getString(R.string.error_playback) - ) - ) + EventBus.send(Event.PlaybackError(getString(R.string.error_playback))) player.next() player.playWhenReady = true diff --git a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt index 23010db..56ba7bf 100644 --- a/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt +++ b/app/src/main/java/com/github/apognu/otter/repositories/HttpUpstream.kt @@ -36,8 +36,6 @@ class HttpUpstream>(val behavior: Behavior, pr .build() .toString() - log(offsetUrl) - get(offsetUrl).fold( { response -> val data = response.getData() diff --git a/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt index 82f5fde..b51ba40 100644 --- a/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt +++ b/app/src/main/java/com/github/apognu/otter/utils/EventBus.kt @@ -38,6 +38,8 @@ sealed class Event { object PlaybackStopped : Event() class Buffering(val value: Boolean) : Event() class TrackPlayed(val track: Track?, val play: Boolean) : Event() + class TrackFinished(val track: Track?) : Event() + class RefreshTrack(val track: Track?, val play: Boolean) : Event() class StateChanged(val playing: Boolean) : Event() object QueueChanged : Event() }