Send track played reports to Funkwhale whenever a track finishes playing. Closes #40.

This commit is contained in:
Antoine POPINEAU 2020-06-01 16:31:58 +02:00
parent dc7803acb4
commit ce05acad21
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
4 changed files with 177 additions and 217 deletions

View File

@ -27,7 +27,10 @@ import com.github.apognu.otter.repositories.FavoritedRepository
import com.github.apognu.otter.repositories.FavoritesRepository import com.github.apognu.otter.repositories.FavoritesRepository
import com.github.apognu.otter.repositories.Repository import com.github.apognu.otter.repositories.Repository
import com.github.apognu.otter.utils.* 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.android.exoplayer2.Player
import com.google.gson.Gson
import com.preference.PowerPreference import com.preference.PowerPreference
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
@ -236,136 +239,9 @@ class MainActivity : AppCompatActivity() {
} }
} }
is Event.TrackPlayed -> { is Event.TrackPlayed -> refreshCurrentTrack(message.track)
message.track?.let { track -> is Event.RefreshTrack -> refreshCurrentTrack(message.track)
if (now_playing.visibility == View.GONE) { is Event.TrackFinished -> incrementListenCount(message.track)
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.StateChanged -> { is Event.StateChanged -> {
when (message.playing) { 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) { private fun changeRepeatMode(index: Int) {
when (index) { when (index) {
// From no repeat to repeat all // 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()
}
}
}
} }

View File

@ -131,17 +131,8 @@ class PlayerService : Service() {
EventBus.send(Event.QueueChanged) EventBus.send(Event.QueueChanged)
if (queue.metadata.isNotEmpty()) { if (queue.metadata.isNotEmpty()) {
EventBus.send( EventBus.send(Event.RefreshTrack(queue.current(), player.playWhenReady))
Event.TrackPlayed( EventBus.send(Event.StateChanged(player.playWhenReady))
queue.current(),
player.playWhenReady
)
)
EventBus.send(
Event.StateChanged(
player.playWhenReady
)
)
} }
} }
@ -154,7 +145,7 @@ class PlayerService : Service() {
state(true) state(true)
EventBus.send( EventBus.send(
Event.TrackPlayed( Event.RefreshTrack(
queue.current(), queue.current(),
true true
) )
@ -172,12 +163,7 @@ class PlayerService : Service() {
state(true) state(true)
EventBus.send( EventBus.send(Event.RefreshTrack(queue.current(),true))
Event.TrackPlayed(
queue.current(),
true
)
)
} }
is Command.ToggleState -> toggle() is Command.ToggleState -> toggle()
@ -211,21 +197,9 @@ class PlayerService : Service() {
jobs.add(GlobalScope.launch(Main) { jobs.add(GlobalScope.launch(Main) {
RequestBus.get().collect { request -> RequestBus.get().collect { request ->
when (request) { when (request) {
is Request.GetCurrentTrack -> request.channel?.offer( is Request.GetCurrentTrack -> request.channel?.offer(Response.CurrentTrack(queue.current()))
Response.CurrentTrack( is Request.GetState -> request.channel?.offer(Response.State(player.playWhenReady))
queue.current() is Request.GetQueue -> request.channel?.offer(Response.Queue(queue.get()))
)
)
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) { if (!state) {
val (progress, _, _) = progress() val (progress, _, _) = progress()
Cache.set( Cache.set(this@PlayerService,"progress", progress.toString().toByteArray())
this@PlayerService,
"progress",
progress.toString().toByteArray()
)
} }
if (state && player.playbackState == Player.STATE_IDLE) { if (state && player.playbackState == Player.STATE_IDLE) {
@ -365,52 +335,27 @@ class PlayerService : Service() {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
super.onPlayerStateChanged(playWhenReady, playbackState) super.onPlayerStateChanged(playWhenReady, playbackState)
EventBus.send( EventBus.send(Event.StateChanged(playWhenReady))
Event.StateChanged(
playWhenReady
)
)
if (queue.current == -1) { if (queue.current == -1) {
EventBus.send( EventBus.send(Event.TrackPlayed(queue.current(), playWhenReady))
Event.TrackPlayed(
queue.current(),
playWhenReady
)
)
} }
when (playWhenReady) { when (playWhenReady) {
true -> { true -> {
when (playbackState) { when (playbackState) {
Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true) Player.STATE_READY -> mediaControlsManager.updateNotification(queue.current(), true)
Player.STATE_BUFFERING -> EventBus.send( Player.STATE_BUFFERING -> EventBus.send(Event.Buffering(true))
Event.Buffering(
true
)
)
Player.STATE_IDLE -> state(false) Player.STATE_IDLE -> state(false)
Player.STATE_ENDED -> EventBus.send(Event.PlaybackStopped) Player.STATE_ENDED -> EventBus.send(Event.PlaybackStopped)
} }
if (playbackState != Player.STATE_BUFFERING) EventBus.send( if (playbackState != Player.STATE_BUFFERING) EventBus.send(Event.Buffering(false))
Event.Buffering(
false
)
)
} }
false -> { false -> {
EventBus.send( EventBus.send(Event.StateChanged(false))
Event.StateChanged( EventBus.send(Event.Buffering(false))
false
)
)
EventBus.send(
Event.Buffering(
false
)
)
if (playbackState == Player.STATE_READY) { if (playbackState == Player.STATE_READY) {
mediaControlsManager.updateNotification(queue.current(), false) mediaControlsManager.updateNotification(queue.current(), false)
@ -435,26 +380,21 @@ class PlayerService : Service() {
} }
} }
Cache.set( Cache.set(this@PlayerService,"current", queue.current.toString().toByteArray() )
this@PlayerService,
"current",
queue.current.toString().toByteArray()
)
EventBus.send( EventBus.send(Event.RefreshTrack(queue.current(),true) )
Event.TrackPlayed( }
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?) { override fun onPlayerError(error: ExoPlaybackException?) {
EventBus.send( EventBus.send(Event.PlaybackError(getString(R.string.error_playback)))
Event.PlaybackError(
getString(R.string.error_playback)
)
)
player.next() player.next()
player.playWhenReady = true player.playWhenReady = true

View File

@ -36,8 +36,6 @@ class HttpUpstream<D : Any, R : FunkwhaleResponse<D>>(val behavior: Behavior, pr
.build() .build()
.toString() .toString()
log(offsetUrl)
get(offsetUrl).fold( get(offsetUrl).fold(
{ response -> { response ->
val data = response.getData() val data = response.getData()

View File

@ -38,6 +38,8 @@ sealed class Event {
object PlaybackStopped : Event() object PlaybackStopped : Event()
class Buffering(val value: Boolean) : Event() class Buffering(val value: Boolean) : Event()
class TrackPlayed(val track: Track?, val play: 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() class StateChanged(val playing: Boolean) : Event()
object QueueChanged : Event() object QueueChanged : Event()
} }