mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
count a play locally when it is scrobbled
A submitted scrobble now bumps the song's playCount/played and its album and artist ranks, so the play sorts move without waiting for a sync. The sync then overwrites them with the server's own numbers, so the increment cannot double-count.
This commit is contained in:
@@ -179,6 +179,50 @@ interface LibraryDao {
|
||||
recomputeArtistPlayData(sourceId)
|
||||
}
|
||||
|
||||
@Query("UPDATE songs SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId AND id = :songId")
|
||||
suspend fun bumpSongPlay(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
at: Long,
|
||||
)
|
||||
|
||||
@Query("UPDATE albums SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId AND id = :albumId")
|
||||
suspend fun bumpAlbumPlay(
|
||||
sourceId: Long,
|
||||
albumId: String,
|
||||
at: Long,
|
||||
)
|
||||
|
||||
@Query(
|
||||
"UPDATE artists SET playCount = playCount + 1, played = :at WHERE sourceId = :sourceId " +
|
||||
"AND id = (SELECT artistId FROM albums WHERE sourceId = :sourceId AND id = :albumId)",
|
||||
)
|
||||
suspend fun bumpArtistPlay(
|
||||
sourceId: Long,
|
||||
albumId: String,
|
||||
at: Long,
|
||||
)
|
||||
|
||||
@Query("SELECT albumId FROM songs WHERE sourceId = :sourceId AND id = :songId")
|
||||
suspend fun albumIdForSong(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
): String?
|
||||
|
||||
// Counted locally when a scrobble is submitted, so the play sorts move before the next sync; the
|
||||
// sync then overwrites with the server's own number.
|
||||
@Transaction
|
||||
suspend fun recordPlay(
|
||||
sourceId: Long,
|
||||
songId: String,
|
||||
at: Long,
|
||||
) {
|
||||
bumpSongPlay(sourceId, songId, at)
|
||||
val albumId = albumIdForSong(sourceId, songId) ?: return
|
||||
bumpAlbumPlay(sourceId, albumId, at)
|
||||
bumpArtistPlay(sourceId, albumId, at)
|
||||
}
|
||||
|
||||
// Albums, not songs: these flows re-run on every write to their table, and songs are written
|
||||
// one batch at a time during a sync.
|
||||
@Query("SELECT EXISTS(SELECT 1 FROM albums WHERE sourceId = :sourceId AND playCount > 0)")
|
||||
|
||||
@@ -217,6 +217,14 @@ class LibraryRepository(
|
||||
songId: String,
|
||||
): Flow<Song?> = db.libraryDao().song(sourceId, songId)
|
||||
|
||||
suspend fun recordPlay(
|
||||
songId: String,
|
||||
at: Long,
|
||||
) {
|
||||
val sourceId = sourceRepository.activeSourceIdOnce() ?: return
|
||||
db.libraryDao().recordPlay(sourceId, songId, at)
|
||||
}
|
||||
|
||||
fun artistAlbums(
|
||||
sourceId: Long,
|
||||
artistId: String,
|
||||
|
||||
@@ -78,7 +78,11 @@ fun appModule(
|
||||
single { PlaybackController(get(), get(), get(), get(), showMessage = toast) }
|
||||
single(createdAtStart = true) {
|
||||
val playback = get<PlaybackController>()
|
||||
Scrobbler(sink = get(), enabled = get<UserPreferences>().scrobbling()).also {
|
||||
Scrobbler(
|
||||
sink = get(),
|
||||
enabled = get<UserPreferences>().scrobbling(),
|
||||
recordPlay = get<LibraryRepository>()::recordPlay,
|
||||
).also {
|
||||
it.attach(playback.state, playback.positionMs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ class Scrobbler(
|
||||
private val enabled: Flow<Boolean>,
|
||||
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
|
||||
private val policy: ScrobblePolicy = ScrobblePolicy(),
|
||||
private val recordPlay: suspend (songId: String, at: Long) -> Unit = { _, _ -> },
|
||||
) {
|
||||
private val submitLock = Mutex()
|
||||
|
||||
@@ -127,8 +128,14 @@ class Scrobbler(
|
||||
private suspend fun submit(event: Scrobble) {
|
||||
try {
|
||||
when (event) {
|
||||
is Scrobble.NowPlaying -> sink.nowPlaying(event.songId)
|
||||
is Scrobble.Submission -> sink.scrobble(event.songId, event.time)
|
||||
is Scrobble.NowPlaying -> {
|
||||
sink.nowPlaying(event.songId)
|
||||
}
|
||||
|
||||
is Scrobble.Submission -> {
|
||||
sink.scrobble(event.songId, event.time)
|
||||
recordPlay(event.songId, event.time / 1000)
|
||||
}
|
||||
}
|
||||
} catch (cancellation: CancellationException) {
|
||||
throw cancellation
|
||||
|
||||
Reference in New Issue
Block a user