Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesigned ListenService #137

Merged
merged 5 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,7 @@ dependencies {

//Jetpack Compose accompanists (https://github.com/google/accompanist)
implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version"

// Util
implementation 'com.github.dariobrux:Timer:1.1.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ListenSubmitBody {
}

private fun Payload.setClientDetails(): Payload{
this.metadata.additionalInfo.submission_client = BuildConfig.APPLICATION_ID
this.metadata.additionalInfo.submission_client = "ListenBrainz Android"
akshaaatt marked this conversation as resolved.
Show resolved Hide resolved
this.metadata.additionalInfo.submission_client_version = BuildConfig.VERSION_NAME
return this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import retrofit2.Call
import retrofit2.Response

class ListenHandler : Handler(Looper.getMainLooper()) {
private val delay = 30000

private val timestamp = "timestamp"

override fun handleMessage(msg: Message) {
Expand Down Expand Up @@ -52,7 +52,7 @@ class ListenHandler : Handler(Looper.getMainLooper()) {
data.putLong(this.timestamp, timestamp)
message.what = timestamp.toInt()
message.data = data
sendMessageDelayed(message, delay.toLong())
sendMessageDelayed(message, 0)
}

fun cancelListen(timestamp: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSessionManager.OnActiveSessionsChangedListener
import android.media.session.PlaybackState
import com.dariobrux.kotimer.Timer
import com.dariobrux.kotimer.interfaces.OnTimerListener
import org.listenbrainz.android.repository.AppPreferences
import org.listenbrainz.android.util.Log.d
import org.listenbrainz.android.util.Log.w
Expand Down Expand Up @@ -53,73 +55,125 @@ class ListenSessionListener(
var artist: String? = null
var title: String? = null
var timestamp: Long = 0
var duration: Long = 0
val timer: Timer = Timer()
var state: PlaybackState? = null
var submitted = true
var submitted = false

// FIXME:
// 1) First ever session song isn't recorded. This is because onMetadataChanged
// isn't called by callback itself.

override fun onMetadataChanged(metadata: MediaMetadata?) {

if (metadata == null) return

// Stop timer and reset metadata.
resetMetadata() // Do not perform this action in timer's onTimerStop due to concurrency issues.
timer.stop()

when {
state != null -> d("onMetadataChanged: Listen Metadata " + state!!.state)
else -> d("onMetadataChanged: Listen Metadata")
}

artist = when {
!metadata.getString(MediaMetadata.METADATA_KEY_ARTIST).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
!metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION)
else -> null
}

title = when {
!metadata.getString(MediaMetadata.METADATA_KEY_TITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
else -> null
}
setArtist(metadata)
setTitle(metadata)

if (artist.isNullOrEmpty() || title.isNullOrEmpty()){
if (isMetadataFaulty()){
w("${if (artist == null) "Artist" else "Title"} is null, listen cancelled.")
return
}

// If the difference between the timestamp of this listen and previously
// submitted listen is less that 1 second, listen should not be submitted.
if ( (System.currentTimeMillis() / 1000 - timestamp) >= 1000) {
submitted = false
}
setDurationAndCallbacks(metadata)

timestamp = System.currentTimeMillis() / 1000
if (state != null && state!!.state == PlaybackState.STATE_PLAYING && !submitted) {
handler.submitListen(artist, title, timestamp)
submitted = true
}
}
// FIXME : Listens are only submitted when song is paused once, then played and skipped.


override fun onPlaybackStateChanged(state: PlaybackState?) {
if (state == null) return

this.state = state
d("onPlaybackStateChanged: Listen PlaybackState " + state.state)

if (isDurationUndefined() || submitted) return

if (state.state == PlaybackState.STATE_PLAYING && !submitted) {

if (artist.isNullOrEmpty() || title.isNullOrEmpty()) return

handler.submitListen(artist, title, timestamp)
submitted = true
if (state.state == PlaybackState.STATE_PLAYING){
timer.start()
// d("Timer started")
}

if (state.state == PlaybackState.STATE_PAUSED ||
state.state == PlaybackState.STATE_STOPPED) {
handler.cancelListen(timestamp)
d("Listen Cancelled.")
artist = ""
title = ""
timestamp = 0
if (state.state == PlaybackState.STATE_PAUSED){
timer.pause()
// d("Timer paused")
}

}

// UTILITY FUNCTIONS

private fun setTitle(metadata: MediaMetadata) {
title = when {
!metadata.getString(MediaMetadata.METADATA_KEY_TITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_TITLE)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE)
else -> null
}
}

private fun setArtist(metadata: MediaMetadata) {
artist = when {
!metadata.getString(MediaMetadata.METADATA_KEY_ARTIST).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
!metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE)
!metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION).isNullOrEmpty() -> metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION)
else -> null
}
}

private fun isMetadataFaulty() : Boolean
= artist.isNullOrEmpty() || title.isNullOrEmpty()

/** Run [artist] and [title] value-check before invoking this function.*/
private fun setDurationAndCallbacks(metadata: MediaMetadata) {
duration = roundDuration(duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) / 2L)
.coerceAtMost(240000) // Since maximum time required to validate a listen as submittable listen is 4 minutes.
timestamp = System.currentTimeMillis() / 1000

// d(duration.toString())
timer.setDuration(duration)

// Setting listener
timer.setOnTimerListener(listener = object : OnTimerListener {
override fun onTimerEnded() {
handler.submitListen(artist, title, timestamp)
submitted = true
}

override fun onTimerPaused(remainingMillis: Long) {
d("${remainingMillis / 1000} seconds left to submit listen.")
}
override fun onTimerRun(milliseconds: Long) {}
override fun onTimerStarted() {}
override fun onTimerStopped() {}

}, callbacksOnMainThread = true)
d("Listener Set")
}

private fun isDurationUndefined() : Boolean
= duration <= 0

private fun resetMetadata() {
d("Metadata Reset")
artist = null
title = null
timestamp = 0
duration = 0
submitted = false
}

private fun roundDuration(duration: Long): Long {
return (duration / 1000) * 1000
}

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@

import android.content.Context
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.*
import java.io.File
import java.io.FileWriter
import java.io.IOException

class CacheService<T>(private val context: Context, private val key: String,private val maxSize:Int=10000) {
fun saveData(value: T, dataType: Class<T>) {
Expand All @@ -18,7 +21,7 @@ class CacheService<T>(private val context: Context, private val key: String,priv
if (!data.contains(value)) {
if (file.length() >= maxSize) {
val newData = data.toMutableList()
newData.removeAt(0)
if (newData.size > 0) newData.removeAt(0)
newData.add(value)
var tostore=gson.toJson(newData)
val fileWriter = FileWriter(file, false)
Expand Down