playlist: add runtime boilerplate

Add the boilerplate code for the playlist system.

Any functionality is runtime only and is not integrated in-app. Plan is
to build the UI scaffold first followed by the backing playlist.
This commit is contained in:
Alexander Capehart 2023-03-19 19:50:34 -06:00
parent abeac90735
commit e4339a76bf
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 147 additions and 8 deletions

View file

@ -144,9 +144,11 @@ interface Indexer {
*
* @param result The outcome of the music loading process.
*/
data class Complete(val result: Result<Library>) : State()
data class Complete(val result: Result<Response>) : State()
}
data class Response(val result: Library, val playlists: List<Playlist>)
/**
* Represents the current progress of the music loader. Usually encapsulated in a [State].
*
@ -237,7 +239,7 @@ constructor(
private val mediaStoreExtractor: MediaStoreExtractor,
private val tagExtractor: TagExtractor
) : Indexer {
@Volatile private var lastResponse: Result<Library>? = null
@Volatile private var lastResponse: Result<Indexer.Response>? = null
@Volatile private var indexingState: Indexer.Indexing? = null
@Volatile private var controller: Indexer.Controller? = null
@Volatile private var listener: Indexer.Listener? = null
@ -303,11 +305,11 @@ constructor(
val result =
try {
val start = System.currentTimeMillis()
val library = indexImpl(context, withCache, this)
val response = indexImpl(context, withCache, this)
logD(
"Music indexing completed successfully in " +
"${System.currentTimeMillis() - start}ms")
Result.success(library)
Result.success(response)
} catch (e: CancellationException) {
// Got cancelled, propagate upwards to top-level co-routine.
logD("Loading routine was cancelled")
@ -337,7 +339,7 @@ constructor(
context: Context,
withCache: Boolean,
scope: CoroutineScope
): Library {
): Indexer.Response {
if (ContextCompat.checkSelfPermission(context, Indexer.PERMISSION_READ_AUDIO) ==
PackageManager.PERMISSION_DENIED) {
logE("Permission check failed")
@ -395,7 +397,7 @@ constructor(
if (cache == null || cache.invalidated) {
cacheRepository.writeCache(rawSongs)
}
return libraryJob.await()
return Indexer.Response(libraryJob.await(), listOf())
}
/**
@ -426,7 +428,7 @@ constructor(
* @param result The new [Result] to emit, representing the outcome of the music loading
* process.
*/
private suspend fun emitCompletion(result: Result<Library>) {
private suspend fun emitCompletion(result: Result<Indexer.Response>) {
yield()
// Swap to the Main thread so that downstream callbacks don't crash from being on
// a background thread. Does not occur in emitIndexing due to efficiency reasons.

View file

@ -40,7 +40,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
entities = [PlaybackState::class, QueueHeapItem::class, QueueMappingItem::class],
version = 27,
exportSchema = false)
@TypeConverters(PersistenceDatabase.Converters::class)
@TypeConverters(Music.UID.Converter::class)
abstract class PersistenceDatabase : RoomDatabase() {
/**
* Get the current [PlaybackStateDao].

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023 Auxio Project
* Playlist.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playlist
import java.util.UUID
import org.oxycblt.auxio.music.Song
interface Playlist {
val id: UUID
val name: String
val songs: List<Song>
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2023 Auxio Project
* PlaylistModule.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playlist
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface PlaylistModule {
@Binds
fun playlistRepository(playlistRepositoryImpl: PlaylistRepositoryImpl): PlaylistRepository
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2023 Auxio Project
* PlaylistRepository.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.playlist
import java.util.UUID
import javax.inject.Inject
import org.oxycblt.auxio.music.Song
interface PlaylistRepository {
val playlists: List<Playlist>
suspend fun createPlaylist(name: String, songs: List<Song>)
suspend fun deletePlaylist(playlist: Playlist)
suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>)
suspend fun removeFromPlaylist(playlist: Playlist, song: Song)
suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>)
}
class PlaylistRepositoryImpl @Inject constructor() : PlaylistRepository {
private val playlistMap = mutableMapOf<UUID, PlaylistImpl>()
override val playlists: List<Playlist>
get() = playlistMap.values.toList()
override suspend fun createPlaylist(name: String, songs: List<Song>) {
val uuid = UUID.randomUUID()
playlistMap[uuid] = PlaylistImpl(uuid, name, songs)
}
override suspend fun deletePlaylist(playlist: Playlist) {
playlistMap.remove(playlist.id)
}
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>) {
editPlaylist(playlist) {
addAll(songs)
this
}
}
override suspend fun removeFromPlaylist(playlist: Playlist, song: Song) {
editPlaylist(playlist) {
remove(song)
this
}
}
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>) {
editPlaylist(playlist) { songs }
}
private inline fun editPlaylist(playlist: Playlist, edits: MutableList<Song>.() -> List<Song>) {
check(playlistMap.containsKey(playlist.id)) { "Invalid playlist argument provided" }
playlistMap[playlist.id] =
PlaylistImpl(playlist.id, playlist.name, edits(playlist.songs.toMutableList()))
}
}
private data class PlaylistImpl(
override val id: UUID,
override val name: String,
override val songs: List<Song>
) : Playlist