diff --git a/app/schemas/me.vanpetegem.accentor.data.AccentorDatabase/8.json b/app/schemas/me.vanpetegem.accentor.data.AccentorDatabase/8.json new file mode 100644 index 00000000..9183911e --- /dev/null +++ b/app/schemas/me.vanpetegem.accentor.data.AccentorDatabase/8.json @@ -0,0 +1,545 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "0a45c6d9ca552cb9e1591bc42ec78cf0", + "entities": [ + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `permission` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permission", + "columnName": "permission", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "albums", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `normalized_title` TEXT NOT NULL, `release` TEXT NOT NULL, `review_comment` TEXT, `edition` TEXT, `edition_description` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `image` TEXT, `image_500` TEXT, `image_250` TEXT, `image_100` TEXT, `image_type` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedTitle", + "columnName": "normalized_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "release", + "columnName": "release", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reviewComment", + "columnName": "review_comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "edition", + "columnName": "edition", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "editionDescription", + "columnName": "edition_description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image500", + "columnName": "image_500", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image250", + "columnName": "image_250", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image100", + "columnName": "image_100", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "imageType", + "columnName": "image_type", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album_artists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` INTEGER NOT NULL, `artist_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `normalized_name` TEXT NOT NULL, `order` INTEGER NOT NULL, `separator` TEXT, PRIMARY KEY(`album_id`, `artist_id`, `name`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedName", + "columnName": "normalized_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "separator", + "columnName": "separator", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "album_id", + "artist_id", + "name" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album_labels", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`album_id` INTEGER NOT NULL, `label_id` INTEGER NOT NULL, `catalogue_number` TEXT, PRIMARY KEY(`album_id`, `label_id`))", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "labelId", + "columnName": "label_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "catalogueNumber", + "columnName": "catalogue_number", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "album_id", + "label_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "artists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `normalized_name` TEXT NOT NULL, `review_comment` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `image` TEXT, `image_500` TEXT, `image_250` TEXT, `image_100` TEXT, `image_type` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedName", + "columnName": "normalized_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reviewComment", + "columnName": "review_comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image500", + "columnName": "image_500", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image250", + "columnName": "image_250", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image100", + "columnName": "image_100", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "imageType", + "columnName": "image_type", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "codec_conversions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `ffmpeg_params` TEXT NOT NULL, `resulting_codec_id` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ffmpegParams", + "columnName": "ffmpeg_params", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "resultingCodecId", + "columnName": "resulting_codec_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "tracks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `title` TEXT NOT NULL, `normalized_title` TEXT NOT NULL, `number` INTEGER NOT NULL, `album_id` INTEGER NOT NULL, `review_comment` TEXT, `created_at` TEXT NOT NULL, `updated_at` TEXT NOT NULL, `codec_id` INTEGER, `length` INTEGER, `bitrate` INTEGER, `location_id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedTitle", + "columnName": "normalized_title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "number", + "columnName": "number", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "album_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reviewComment", + "columnName": "review_comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "updatedAt", + "columnName": "updated_at", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "codecId", + "columnName": "codec_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "length", + "columnName": "length", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "locationId", + "columnName": "location_id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "track_artists", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`track_id` INTEGER NOT NULL, `artist_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `normalized_name` TEXT NOT NULL, `role` INTEGER NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`track_id`, `artist_id`, `name`, `role`))", + "fields": [ + { + "fieldPath": "trackId", + "columnName": "track_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artist_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "normalizedName", + "columnName": "normalized_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "role", + "columnName": "role", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "track_id", + "artist_id", + "name", + "role" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "track_genres", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`track_id` INTEGER NOT NULL, `genre_id` INTEGER NOT NULL, PRIMARY KEY(`track_id`, `genre_id`))", + "fields": [ + { + "fieldPath": "trackId", + "columnName": "track_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "genreId", + "columnName": "genre_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "track_id", + "genre_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "unreported_plays", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `track_id` INTEGER NOT NULL, `played_at` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trackId", + "columnName": "track_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playedAt", + "columnName": "played_at", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0a45c6d9ca552cb9e1591bc42ec78cf0')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt b/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt new file mode 100644 index 00000000..6b5da547 --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/api/plays/Play.kt @@ -0,0 +1,25 @@ +package me.vanpetegem.accentor.api.plays + +import com.github.kittinunf.fuel.httpPost +import java.time.Instant +import me.vanpetegem.accentor.data.authentication.AuthenticationData +import me.vanpetegem.accentor.data.plays.Play +import me.vanpetegem.accentor.util.Result +import me.vanpetegem.accentor.util.jsonBody +import me.vanpetegem.accentor.util.responseObject + +data class Arguments(val play: PlayArguments) +data class PlayArguments(val trackId: Int, val playedAt: Instant) + +fun create(server: String, authenticationData: AuthenticationData, trackId: Int, playedAt: Instant): Result { + return "$server/api/plays".httpPost() + .set("Accept", "application/json") + .set("X-Secret", authenticationData.secret) + .set("X-Device-Id", authenticationData.deviceId) + .jsonBody(Arguments(PlayArguments(trackId, playedAt))) + .responseObject().third + .fold( + { play: Play -> Result.Success(play) }, + { e: Throwable -> Result.Error(Exception("Error creating play", e)) }, + ) +} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt b/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt index f0935391..b7e120b1 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/AccentorDatabase.kt @@ -20,6 +20,8 @@ import me.vanpetegem.accentor.data.artists.ArtistDao import me.vanpetegem.accentor.data.artists.DbArtist import me.vanpetegem.accentor.data.codecconversions.CodecConversionDao import me.vanpetegem.accentor.data.codecconversions.DbCodecConversion +import me.vanpetegem.accentor.data.plays.UnreportedPlay +import me.vanpetegem.accentor.data.plays.UnreportedPlayDao import me.vanpetegem.accentor.data.tracks.DbTrack import me.vanpetegem.accentor.data.tracks.DbTrackArtist import me.vanpetegem.accentor.data.tracks.DbTrackGenre @@ -40,8 +42,9 @@ import me.vanpetegem.accentor.util.RoomTypeConverters DbTrack::class, DbTrackArtist::class, DbTrackGenre::class, + UnreportedPlay::class, ], - version = 7 + version = 8 ) abstract class AccentorDatabase : RoomDatabase() { abstract fun albumDao(): AlbumDao @@ -49,6 +52,7 @@ abstract class AccentorDatabase : RoomDatabase() { abstract fun codecConversionDao(): CodecConversionDao abstract fun trackDao(): TrackDao abstract fun userDao(): UserDao + abstract fun unreportedPlayDao(): UnreportedPlayDao } @Module @@ -180,6 +184,25 @@ internal object DatabaseModule { } } }) + .addMigrations(object : Migration(7, 8) { + override fun migrate(database: SupportSQLiteDatabase) { + database.beginTransaction() + try { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `unreported_plays` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `track_id` INTEGER NOT NULL, + `played_at` TEXT NOT NULL + ) + """ + ) + database.setTransactionSuccessful() + } finally { + database.endTransaction() + } + } + }) .build() } @@ -193,4 +216,6 @@ internal object DatabaseModule { fun provideTrackDao(database: AccentorDatabase): TrackDao = database.trackDao() @Provides fun provideUserDao(database: AccentorDatabase): UserDao = database.userDao() + @Provides + fun provideUnreportedPlayDao(database: AccentorDatabase): UnreportedPlayDao = database.unreportedPlayDao() } diff --git a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt index 91c43821..d234de1a 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/codecconversions/CodecConversionRepository.kt @@ -19,6 +19,9 @@ class CodecConversionRepository @Inject constructor( map } + fun getFirst(): CodecConversion? = codecConversionDao.getFirstCodecConversion() + fun getById(id: Int): CodecConversion? = codecConversionDao.getCodecConversionById(id) + suspend fun refresh(handler: suspend (Result) -> Unit) { when (val result = index(authenticationRepository.server.value!!, authenticationRepository.authData.value!!)) { is Result.Success -> { diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt new file mode 100644 index 00000000..f2af5d94 --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/Play.kt @@ -0,0 +1,10 @@ +package me.vanpetegem.accentor.data.plays + +import java.time.Instant + +data class Play( + val id: Int, + val playedAt: Instant, + val trackId: Int, + val userId: Int, +) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt new file mode 100644 index 00000000..226bc96e --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/PlayRepository.kt @@ -0,0 +1,27 @@ +package me.vanpetegem.accentor.data.plays + +import java.time.Instant +import javax.inject.Inject +import me.vanpetegem.accentor.api.plays.create +import me.vanpetegem.accentor.data.authentication.AuthenticationRepository +import me.vanpetegem.accentor.util.Result + +class PlayRepository @Inject constructor( + private val authenticationRepository: AuthenticationRepository, + private val unreportedPlayDao: UnreportedPlayDao, +) { + suspend fun reportPlay(trackId: Int, playedAt: Instant) { + when (create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, trackId, playedAt)) { + is Result.Success -> { + for (play in unreportedPlayDao.getAllUnreportedPlays()) { + when (create(authenticationRepository.server.value!!, authenticationRepository.authData.value!!, play.trackId, play.playedAt)) { + is Result.Success -> unreportedPlayDao.delete(play) + } + } + } + is Result.Error -> { + unreportedPlayDao.insert(UnreportedPlay(trackId = trackId, playedAt = playedAt)) + } + } + } +} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt new file mode 100644 index 00000000..31d9f081 --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlay.kt @@ -0,0 +1,16 @@ +package me.vanpetegem.accentor.data.plays + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.time.Instant + +@Entity(tableName = "unreported_plays") +data class UnreportedPlay( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + @ColumnInfo(name = "track_id") + val trackId: Int, + @ColumnInfo(name = "played_at") + val playedAt: Instant, +) diff --git a/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlayDao.kt b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlayDao.kt new file mode 100644 index 00000000..e76ff52e --- /dev/null +++ b/app/src/main/java/me/vanpetegem/accentor/data/plays/UnreportedPlayDao.kt @@ -0,0 +1,18 @@ +package me.vanpetegem.accentor.data.plays + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query + +@Dao +interface UnreportedPlayDao { + @Query("SELECT * FROM unreported_plays") + fun getAllUnreportedPlays(): List + + @Insert + fun insert(play: UnreportedPlay) + + @Delete + fun delete(play: UnreportedPlay) +} diff --git a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt index b6990445..4b35d8ce 100644 --- a/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt +++ b/app/src/main/java/me/vanpetegem/accentor/data/tracks/TrackRepository.kt @@ -15,6 +15,7 @@ class TrackRepository @Inject constructor( private val authenticationRepository: AuthenticationRepository ) { fun findById(id: Int): LiveData = trackDao.findById(id) + fun getById(id: Int): Track? = trackDao.getTrackById(id) fun findByIds(ids: List): LiveData> = trackDao.findByIds(ids) fun findByArtist(artist: Artist): LiveData> = trackDao.findByArtist(artist) fun findByAlbum(album: Album): LiveData> = trackDao.findByAlbum(album) diff --git a/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt b/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt index 274a8a5c..792b43d5 100644 --- a/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt +++ b/app/src/main/java/me/vanpetegem/accentor/media/MusicService.kt @@ -14,6 +14,7 @@ import androidx.media2.session.SessionCommandGroup import androidx.media2.session.SessionResult import com.google.android.exoplayer2.C import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.database.ExoDatabaseProvider @@ -29,6 +30,7 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto import com.google.android.exoplayer2.upstream.cache.SimpleCache import dagger.hilt.android.AndroidEntryPoint import java.io.File +import java.time.Instant import java.util.concurrent.Executors import javax.inject.Inject import kotlinx.coroutines.Dispatchers.IO @@ -37,12 +39,13 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import me.vanpetegem.accentor.R import me.vanpetegem.accentor.data.albums.Album -import me.vanpetegem.accentor.data.albums.AlbumDao +import me.vanpetegem.accentor.data.albums.AlbumRepository import me.vanpetegem.accentor.data.authentication.AuthenticationDataSource -import me.vanpetegem.accentor.data.codecconversions.CodecConversionDao +import me.vanpetegem.accentor.data.codecconversions.CodecConversionRepository +import me.vanpetegem.accentor.data.plays.PlayRepository import me.vanpetegem.accentor.data.preferences.PreferencesDataSource import me.vanpetegem.accentor.data.tracks.Track -import me.vanpetegem.accentor.data.tracks.TrackDao +import me.vanpetegem.accentor.data.tracks.TrackRepository import me.vanpetegem.accentor.userAgent @AndroidEntryPoint @@ -51,9 +54,10 @@ class MusicService : MediaSessionService() { @Inject lateinit var authenticationDataSource: AuthenticationDataSource @Inject lateinit var preferencesDataSource: PreferencesDataSource - @Inject lateinit var codecConversionDao: CodecConversionDao - @Inject lateinit var trackDao: TrackDao - @Inject lateinit var albumDao: AlbumDao + @Inject lateinit var codecConversionRepository: CodecConversionRepository + @Inject lateinit var trackRepository: TrackRepository + @Inject lateinit var albumRepository: AlbumRepository + @Inject lateinit var playRepository: PlayRepository private lateinit var notificationManager: NotificationManagerCompat private lateinit var notificationBuilder: NotificationBuilder @@ -95,6 +99,30 @@ class MusicService : MediaSessionService() { setHandleAudioBecomingNoisy(true) }.build().apply { setAudioAttributes(accentorAudioAttributes, true) + addListener(object : Player.Listener { + private var trackId: Int? = null + + override fun onMediaItemTransition(item: com.google.android.exoplayer2.MediaItem?, reason: Int) { + trackId = item?.mediaId?.toInt() + } + + override fun onPositionDiscontinuity(old: Player.PositionInfo, new: Player.PositionInfo, reason: Int) { + if (trackId != null && reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) { + reportPlay() + } + } + + override fun onPlaybackStateChanged(state: Int) { + if (trackId != null && state == Player.STATE_ENDED) { + reportPlay() + } + } + + private fun reportPlay() { + val savedTrackId = trackId!! + mainScope.launch(IO) { playRepository.reportPlay(savedTrackId, Instant.now()) } + } + }) } } @@ -109,8 +137,8 @@ class MusicService : MediaSessionService() { info: MediaSession.ControllerInfo, mediaId: String ): MediaItem? { - val track = trackDao.getTrackById(mediaId.toInt()) - val album = track?.let { albumDao.getAlbumById(it.albumId) } + val track = trackRepository.getById(mediaId.toInt()) + val album = track?.let { albumRepository.getById(it.albumId) } return track?.let { t -> album?.let { a -> convertTrack(t, a) } } } } @@ -181,8 +209,8 @@ class MusicService : MediaSessionService() { private fun convertTrack(track: Track, album: Album): MediaItem { val conversionId = preferencesDataSource.conversionId.value - val firstConversion by lazy { codecConversionDao.getFirstCodecConversion() } - val conversionParam = if (conversionId != null && codecConversionDao.getCodecConversionById(conversionId) != null) { + val firstConversion by lazy { codecConversionRepository.getFirst() } + val conversionParam = if (conversionId != null && codecConversionRepository.getById(conversionId) != null) { "&codec_conversion_id=$conversionId" } else if (firstConversion != null) { "&codec_conversion_id=${firstConversion!!.id}"