Rewrite Music Loading a g a i n
Rewrite the music loading for [hopefully] the final time, now with Artists/Albums/Media instead of just AudioColumns, improved genre loading, and easier to implement album-art. Hopefully this system will stick.
This commit is contained in:
parent
160013bbe9
commit
b1be2802cf
9 changed files with 366 additions and 215 deletions
38
app/src/main/java/org/oxycblt/auxio/music/GenreCompat.kt
Normal file
38
app/src/main/java/org/oxycblt/auxio/music/GenreCompat.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
package org.oxycblt.auxio.music
|
||||
|
||||
// Compatibility layer to convert old int-based genres to new genres
|
||||
val ID3_GENRES = arrayOf<String>(
|
||||
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz",
|
||||
"Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno",
|
||||
"Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno",
|
||||
"Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental",
|
||||
"Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk",
|
||||
"Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave",
|
||||
"Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy",
|
||||
"Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
|
||||
"Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
|
||||
"Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock",
|
||||
|
||||
// Winamp extensions
|
||||
"Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival",
|
||||
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock",
|
||||
"Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour",
|
||||
"Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
|
||||
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad",
|
||||
"Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella",
|
||||
"Euro-House", "Dance Hall", "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
|
||||
"Britpop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta", "Heavy Metal", "Black Metal",
|
||||
"Crossover", "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal",
|
||||
"Anime", "JPop", "Synthpop"
|
||||
)
|
||||
|
||||
const val PAREN_FILTER = "()"
|
||||
|
||||
fun intToNamedGenre(genre: String): String? {
|
||||
// Strip the genres of any parentheses, and convert it to an int
|
||||
val intGenre = genre.filterNot {
|
||||
PAREN_FILTER.indexOf(it) > -1
|
||||
}.toInt()
|
||||
|
||||
return ID3_GENRES.getOrNull(intGenre)
|
||||
}
|
|
@ -3,10 +3,14 @@ package org.oxycblt.auxio.music
|
|||
import android.app.Application
|
||||
import android.content.ContentResolver
|
||||
import android.database.Cursor
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.Albums
|
||||
import android.provider.MediaStore.Audio.Artists
|
||||
import android.provider.MediaStore.Audio.Genres
|
||||
import android.provider.MediaStore.Audio.AudioColumns
|
||||
import android.provider.MediaStore.Audio.Media
|
||||
import android.util.Log
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Genre
|
||||
import org.oxycblt.auxio.music.models.Song
|
||||
|
||||
enum class MusicLoaderResponse {
|
||||
|
@ -17,62 +21,44 @@ enum class MusicLoaderResponse {
|
|||
// FIXME: This thing probably has some memory leaks *somewhere*
|
||||
class MusicLoader(private val app: Application) {
|
||||
|
||||
var genres = mutableListOf<Genre>()
|
||||
var artists = mutableListOf<Artist>()
|
||||
var albums = mutableListOf<Album>()
|
||||
var songs = mutableListOf<Song>()
|
||||
var genres = mutableListOf<Pair<Long, String>>()
|
||||
|
||||
private var musicCursor: Cursor? = null
|
||||
private var genreCursor: Cursor? = null
|
||||
private var artistCursor: Cursor? = null
|
||||
private var albumCursor: Cursor? = null
|
||||
private var songCursor: Cursor? = null
|
||||
|
||||
val response: MusicLoaderResponse
|
||||
val resolver: ContentResolver = app.contentResolver
|
||||
|
||||
init {
|
||||
response = findMusic()
|
||||
}
|
||||
|
||||
private fun findMusic(): MusicLoaderResponse {
|
||||
genreCursor = getGenreCursor(
|
||||
app.contentResolver
|
||||
)
|
||||
|
||||
useGenreCursor()
|
||||
|
||||
indexMusic()
|
||||
|
||||
try {
|
||||
loadGenres()
|
||||
loadArtists()
|
||||
loadAlbums()
|
||||
loadSongs()
|
||||
} catch (error: Exception) {
|
||||
Log.e(this::class.simpleName, "Something went horribly wrong.")
|
||||
error.printStackTrace()
|
||||
|
||||
musicCursor?.close()
|
||||
|
||||
return MusicLoaderResponse.FAILURE
|
||||
}
|
||||
|
||||
// If the main loading completed without a failure, return DONE or
|
||||
// NO_MUSIC depending on if any music was found.
|
||||
return if (songs.size > 0) {
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Successfully found " + songs.size.toString() + " Songs."
|
||||
)
|
||||
|
||||
MusicLoaderResponse.DONE
|
||||
} else {
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"No music was found."
|
||||
)
|
||||
|
||||
MusicLoaderResponse.NO_MUSIC
|
||||
}
|
||||
return MusicLoaderResponse.DONE
|
||||
}
|
||||
|
||||
private fun getGenreCursor(resolver: ContentResolver): Cursor? {
|
||||
Log.i(this::class.simpleName, "Getting genre cursor.")
|
||||
private fun loadGenres() {
|
||||
Log.d(this::class.simpleName, "Starting genre search...")
|
||||
|
||||
// Get every Genre indexed by the android system, for some reason
|
||||
// you cant directly get this from the plain music cursor.
|
||||
return resolver.query(
|
||||
// First, get a cursor for every genre in the android system
|
||||
genreCursor = resolver.query(
|
||||
Genres.EXTERNAL_CONTENT_URI,
|
||||
arrayOf(
|
||||
Genres._ID, // 0
|
||||
|
@ -81,116 +67,212 @@ class MusicLoader(private val app: Application) {
|
|||
null, null,
|
||||
Genres.DEFAULT_SORT_ORDER
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMusicCursor(resolver: ContentResolver, genreId: Long): Cursor? {
|
||||
Log.i(this::class.simpleName, "Getting music cursor.")
|
||||
|
||||
// Get all the values that can be retrieved by the cursor.
|
||||
// The rest are retrieved using MediaMetadataExtractor and
|
||||
// stored into a database.
|
||||
return resolver.query(
|
||||
Genres.Members.getContentUri("external", genreId),
|
||||
arrayOf(
|
||||
AudioColumns._ID, // 0
|
||||
AudioColumns.DISPLAY_NAME, // 1
|
||||
AudioColumns.TITLE, // 2
|
||||
AudioColumns.ARTIST, // 3
|
||||
AudioColumns.ALBUM, // 4
|
||||
AudioColumns.YEAR, // 5
|
||||
AudioColumns.TRACK, // 6
|
||||
AudioColumns.DURATION // 7
|
||||
),
|
||||
AudioColumns.IS_MUSIC + "=1", null,
|
||||
MediaStore.Audio.Media.DEFAULT_SORT_ORDER
|
||||
)
|
||||
}
|
||||
|
||||
// Use the genre cursor to index all the genres the android system has indexed.
|
||||
private fun useGenreCursor() {
|
||||
// TODO: Move genre to its own model, even if its just two values
|
||||
|
||||
// And then process those into Genre objects
|
||||
genreCursor?.use { cursor ->
|
||||
|
||||
// Don't even bother running if there's nothing to process.
|
||||
if (cursor.count == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val idIndex = cursor.getColumnIndexOrThrow(Genres._ID)
|
||||
val nameIndex = cursor.getColumnIndexOrThrow(Genres.NAME)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
genres.add(
|
||||
Pair<Long, String>(
|
||||
cursor.getLong(idIndex),
|
||||
cursor.getString(nameIndex)
|
||||
)
|
||||
)
|
||||
val id = cursor.getLong(idIndex)
|
||||
var name = cursor.getString(nameIndex)
|
||||
|
||||
Log.i(this::class.simpleName, cursor.getString(nameIndex))
|
||||
// If a genre is still in an old int-based format [Android formats it as "(INT)"],
|
||||
// convert that to the corresponding ID3 genre.
|
||||
if (name.contains("[0-9][()]")) {
|
||||
name = intToNamedGenre(name)
|
||||
}
|
||||
|
||||
genres.add(
|
||||
Genre(
|
||||
id, name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
Log.i(this::class.simpleName, "Retrieved " + genres.size.toString() + " Genres.")
|
||||
// Remove dupes
|
||||
genres = genres.distinctBy {
|
||||
it.name
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Genre search finished with " +
|
||||
genres.size.toString() +
|
||||
" genres found."
|
||||
)
|
||||
}
|
||||
|
||||
// Use the cursor index music files from the shared storage.
|
||||
private fun indexMusic() {
|
||||
Log.i(this::class.simpleName, "Starting music search...")
|
||||
private fun loadArtists() {
|
||||
Log.d(this::class.simpleName, "Starting artist search...")
|
||||
|
||||
// So, android has a brain aneurysm if you try to get an audio genre through
|
||||
// AudioColumns. As a result, I just index every genre's name & ID, create a cursor
|
||||
// of music for that genres ID, and then load and iterate through them normally,
|
||||
// creating using the genres name as that songs Genre.
|
||||
|
||||
// God why do I have to do this
|
||||
// To associate artists with their genres, a new cursor is
|
||||
// created with all the artists of that type.
|
||||
for (genre in genres) {
|
||||
val musicCursor = getMusicCursor(app.contentResolver, genre.first)
|
||||
artistCursor = resolver.query(
|
||||
Genres.Members.getContentUri("external", genre.id),
|
||||
arrayOf(
|
||||
Artists._ID,
|
||||
Artists.ARTIST
|
||||
),
|
||||
null, null,
|
||||
Artists.DEFAULT_SORT_ORDER
|
||||
)
|
||||
|
||||
musicCursor?.use { cursor ->
|
||||
|
||||
// Don't run the more expensive file loading operations if there
|
||||
// is no music to index.
|
||||
if (cursor.count == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID)
|
||||
val displayIndex = cursor.getColumnIndexOrThrow(AudioColumns.DISPLAY_NAME)
|
||||
val titleIndex = cursor.getColumnIndexOrThrow(AudioColumns.TITLE)
|
||||
val artistIndex = cursor.getColumnIndexOrThrow(AudioColumns.ARTIST)
|
||||
val albumIndex = cursor.getColumnIndexOrThrow(AudioColumns.ALBUM)
|
||||
val yearIndex = cursor.getColumnIndexOrThrow(AudioColumns.YEAR)
|
||||
val trackIndex = cursor.getColumnIndexOrThrow(AudioColumns.TRACK)
|
||||
val durationIndex = cursor.getColumnIndexOrThrow(AudioColumns.DURATION)
|
||||
artistCursor?.use { cursor ->
|
||||
val idIndex = cursor.getColumnIndexOrThrow(Artists._ID)
|
||||
val nameIndex = cursor.getColumnIndexOrThrow(Artists.ARTIST)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val display = cursor.getString(displayIndex)
|
||||
val name = cursor.getString(nameIndex)
|
||||
|
||||
// Get the basic metadata from the cursor
|
||||
val title = cursor.getString(titleIndex) ?: display
|
||||
// If an artist has already been added [Which is very likely due to how genres
|
||||
// are processed], add the genre to the existing artist instead of creating a
|
||||
// new one.
|
||||
val existingArtist = artists.find { it.name == name }
|
||||
|
||||
if (existingArtist != null) {
|
||||
existingArtist.genres.add(genre.name)
|
||||
} else {
|
||||
artists.add(
|
||||
Artist(
|
||||
id, name,
|
||||
mutableListOf(genre.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dupes [Just in case]
|
||||
artists = artists.distinctBy {
|
||||
it.name to it.genres
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Artist search finished with " +
|
||||
artists.size.toString() +
|
||||
" artists found."
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadAlbums() {
|
||||
Log.d(this::class.simpleName, "Starting album search...")
|
||||
|
||||
albumCursor = resolver.query(
|
||||
Albums.EXTERNAL_CONTENT_URI,
|
||||
arrayOf(
|
||||
Albums._ID,
|
||||
Albums.ALBUM,
|
||||
Albums.ARTIST,
|
||||
|
||||
// FIXME: May be an issue for albums whose songs released in multiple years
|
||||
Albums.FIRST_YEAR,
|
||||
Albums.NUMBER_OF_SONGS
|
||||
),
|
||||
null, null,
|
||||
Albums.DEFAULT_SORT_ORDER
|
||||
)
|
||||
|
||||
albumCursor?.use { cursor ->
|
||||
val idIndex = cursor.getColumnIndexOrThrow(Albums._ID)
|
||||
val nameIndex = cursor.getColumnIndexOrThrow(Albums.ALBUM)
|
||||
val artistIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST)
|
||||
val yearIndex = cursor.getColumnIndexOrThrow(Albums.FIRST_YEAR)
|
||||
val numIndex = cursor.getColumnIndexOrThrow(Albums.NUMBER_OF_SONGS)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex)
|
||||
val artist = cursor.getString(artistIndex)
|
||||
val album = cursor.getString(albumIndex)
|
||||
val year = cursor.getInt(yearIndex)
|
||||
val track = cursor.getInt(trackIndex)
|
||||
val duration = cursor.getLong(durationIndex)
|
||||
val numSongs = cursor.getInt(numIndex)
|
||||
|
||||
// TODO: Add album art [But its loaded separately, as that will take a bit]
|
||||
// TODO: Add genres whenever android hasn't borked it
|
||||
songs.add(
|
||||
Song(
|
||||
id, title, artist, album,
|
||||
genre.second, year, track, duration
|
||||
albums.add(
|
||||
Album(
|
||||
id, name, artist,
|
||||
year, numSongs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Remove dupes
|
||||
albums = albums.distinctBy {
|
||||
it.title to it.artistName to it.year to it.numSongs
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Album search finished with " +
|
||||
albums.size.toString() +
|
||||
" albums found."
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadSongs() {
|
||||
Log.d(this::class.simpleName, "Starting song search...")
|
||||
|
||||
songCursor = resolver.query(
|
||||
Media.EXTERNAL_CONTENT_URI,
|
||||
arrayOf(
|
||||
Media._ID, // 0
|
||||
Media.DISPLAY_NAME, // 1
|
||||
Media.TITLE, // 2
|
||||
Media.ALBUM, // 4
|
||||
Media.TRACK, // 6
|
||||
Media.DURATION // 7
|
||||
),
|
||||
Media.IS_MUSIC + "=1", null,
|
||||
Media.DEFAULT_SORT_ORDER
|
||||
)
|
||||
|
||||
songCursor?.use { cursor ->
|
||||
val idIndex = cursor.getColumnIndexOrThrow(Media._ID)
|
||||
val fileIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)
|
||||
val titleIndex = cursor.getColumnIndexOrThrow(Media.TITLE)
|
||||
val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM)
|
||||
val trackIndex = cursor.getColumnIndexOrThrow(Media.TRACK)
|
||||
val durationIndex = cursor.getColumnIndexOrThrow(Media.DURATION)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val title = cursor.getString(titleIndex) ?: cursor.getString(fileIndex)
|
||||
val album = cursor.getString(albumIndex)
|
||||
val track = cursor.getInt(trackIndex)
|
||||
val duration = cursor.getLong(durationIndex)
|
||||
|
||||
songs.add(
|
||||
Song(
|
||||
id, title, album,
|
||||
track, duration
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
// Remove dupes
|
||||
songs = songs.distinctBy {
|
||||
it.title to it.albumName to it.track to it.duration
|
||||
}.toMutableList()
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Song search finished with " +
|
||||
songs.size.toString() +
|
||||
" songs found."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,15 +22,25 @@ class MusicRepository {
|
|||
val songs: LiveData<List<Song>> get() = mSongs
|
||||
|
||||
suspend fun init(app: Application): MusicLoaderResponse {
|
||||
Log.i(this::class.simpleName, "Starting initial music load")
|
||||
|
||||
val loader = MusicLoader(app)
|
||||
|
||||
if (loader.response == MusicLoaderResponse.DONE) {
|
||||
// If the loading succeeds, then process the songs into lists of
|
||||
// songs, albums, and artists on the main thread.
|
||||
withContext(Dispatchers.Main) {
|
||||
mSongs.value = processSongs(loader.songs)
|
||||
mAlbums.value = sortIntoAlbums(mSongs.value as MutableList<Song>)
|
||||
mArtists.value = sortIntoArtists(mAlbums.value as MutableList<Album>)
|
||||
val sorter = MusicSorter(
|
||||
loader.artists,
|
||||
loader.albums,
|
||||
loader.songs
|
||||
)
|
||||
|
||||
mSongs.value = sorter.songs
|
||||
mAlbums.value = sorter.albums
|
||||
mArtists.value = sorter.artists
|
||||
|
||||
Log.i(this::class.simpleName, "Finished initial music load.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
92
app/src/main/java/org/oxycblt/auxio/music/MusicSorter.kt
Normal file
92
app/src/main/java/org/oxycblt/auxio/music/MusicSorter.kt
Normal file
|
@ -0,0 +1,92 @@
|
|||
package org.oxycblt.auxio.music
|
||||
|
||||
import android.util.Log
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Song
|
||||
|
||||
class MusicSorter(
|
||||
val artists: MutableList<Artist>,
|
||||
val albums: MutableList<Album>,
|
||||
val songs: MutableList<Song>
|
||||
) {
|
||||
init {
|
||||
sortSongsIntoAlbums()
|
||||
sortAlbumsIntoArtists()
|
||||
}
|
||||
|
||||
private fun sortSongsIntoAlbums() {
|
||||
Log.d(this::class.simpleName, "Sorting songs into albums...")
|
||||
|
||||
val unknownSongs = songs.toMutableList()
|
||||
|
||||
for (album in albums) {
|
||||
// Find all songs that match the current album title
|
||||
val albumSongs = songs.filter { it.albumName == album.title }
|
||||
|
||||
// And then add them to the album
|
||||
album.songs.addAll(albumSongs)
|
||||
|
||||
unknownSongs.removeAll(albumSongs)
|
||||
}
|
||||
|
||||
// Any remaining songs will be added to an unknown album
|
||||
if (unknownSongs.size > 0) {
|
||||
|
||||
// Reuse an existing unknown albumif one is found
|
||||
val unknownAlbum = albums.find { it.title == null } ?: Album()
|
||||
|
||||
unknownAlbum.songs.addAll(unknownSongs)
|
||||
unknownAlbum.numSongs = unknownAlbum.songs.size
|
||||
|
||||
for (song in unknownSongs) {
|
||||
song.album = unknownAlbum
|
||||
}
|
||||
|
||||
albums.add(unknownAlbum)
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Placed " + unknownSongs.size.toString() + " songs into an unknown album"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortAlbumsIntoArtists() {
|
||||
Log.d(this::class.simpleName, "Sorting albums into artists...")
|
||||
|
||||
val unknownAlbums = albums.toMutableList()
|
||||
|
||||
for (artist in artists) {
|
||||
// Find all albums that match the current artist name
|
||||
val artistAlbums = albums.filter { it.artistName == artist.name }
|
||||
|
||||
// And then add them to the album, along with refreshing the amount of albums
|
||||
artist.albums.addAll(artistAlbums)
|
||||
artist.numAlbums = artist.albums.size
|
||||
|
||||
unknownAlbums.removeAll(artistAlbums)
|
||||
}
|
||||
|
||||
// Any remaining albums will be added to an unknown artist
|
||||
if (unknownAlbums.size > 0) {
|
||||
|
||||
// Reuse an existing unknown artist if one is found
|
||||
val unknownArtist = artists.find { it.name == null } ?: Artist()
|
||||
|
||||
unknownArtist.albums.addAll(unknownAlbums)
|
||||
unknownArtist.numAlbums = albums.size
|
||||
|
||||
for (album in unknownAlbums) {
|
||||
album.artist = unknownArtist
|
||||
}
|
||||
|
||||
artists.add(unknownArtist)
|
||||
|
||||
Log.d(
|
||||
this::class.simpleName,
|
||||
"Placed " + unknownAlbums.size.toString() + " albums into an unknown album"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package org.oxycblt.auxio.music
|
||||
|
||||
import org.oxycblt.auxio.music.models.Album
|
||||
import org.oxycblt.auxio.music.models.Artist
|
||||
import org.oxycblt.auxio.music.models.Song
|
||||
|
||||
// Sort a list of Song objects into lists of songs, albums, and artists.
|
||||
fun processSongs(songs: MutableList<Song>): MutableList<Song> {
|
||||
// Eliminate all duplicates from the list
|
||||
// excluding the ID, as that's guaranteed to be unique [I think]
|
||||
return songs.distinctBy {
|
||||
it.name to it.artist to it.album to it.year to it.track to it.duration
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
// Sort a list of song objects into albums
|
||||
fun sortIntoAlbums(songs: MutableList<Song>): MutableList<Album> {
|
||||
val songsByAlbum = songs.groupBy { it.album }
|
||||
val albumList = mutableListOf<Album>()
|
||||
|
||||
songsByAlbum.keys.iterator().forEach { album ->
|
||||
val albumSongs = songsByAlbum[album]
|
||||
albumSongs?.let {
|
||||
albumList.add(
|
||||
Album(albumSongs)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return albumList
|
||||
}
|
||||
|
||||
// Sort a list of album objects into artists
|
||||
fun sortIntoArtists(albums: MutableList<Album>): MutableList<Artist> {
|
||||
val albumsByArtist = albums.groupBy { it.artist }
|
||||
val artistList = mutableListOf<Artist>()
|
||||
|
||||
albumsByArtist.keys.iterator().forEach { artist ->
|
||||
val artistAlbums = albumsByArtist[artist]
|
||||
|
||||
artistAlbums?.let {
|
||||
artistList.add(
|
||||
Artist(artistAlbums)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return artistList
|
||||
}
|
|
@ -2,30 +2,13 @@ package org.oxycblt.auxio.music.models
|
|||
|
||||
// Abstraction for Song
|
||||
data class Album(
|
||||
var songs: List<Song>
|
||||
val id: Long = 0L,
|
||||
val title: String? = null,
|
||||
val artistName: String? = null,
|
||||
val year: Int = 0,
|
||||
var numSongs: Int = 0
|
||||
) {
|
||||
var title: String? = null
|
||||
var artist: String? = null
|
||||
var year: Int = 0
|
||||
lateinit var artist: Artist
|
||||
|
||||
init {
|
||||
// Iterate through the child songs and inherit the first valid value
|
||||
// for the Album Name, Artist, and Year
|
||||
for (song in songs) {
|
||||
if (song.album != null) {
|
||||
title = song.album
|
||||
}
|
||||
|
||||
if (song.artist != null) {
|
||||
artist = song.artist
|
||||
}
|
||||
|
||||
if (song.year != 0) {
|
||||
year = song.year
|
||||
}
|
||||
}
|
||||
|
||||
// Also sort the songs by track
|
||||
songs = songs.sortedBy { it.track }
|
||||
}
|
||||
val songs = mutableListOf<Song>()
|
||||
}
|
||||
|
|
|
@ -2,19 +2,10 @@ package org.oxycblt.auxio.music.models
|
|||
|
||||
// Abstraction for mAlbums
|
||||
data class Artist(
|
||||
private var albums: List<Album>
|
||||
val id: Long = 0,
|
||||
val name: String? = null,
|
||||
val genres: MutableList<String?> = mutableListOf(null)
|
||||
) {
|
||||
var name: String? = null
|
||||
|
||||
init {
|
||||
// Like Album, iterate through the child albums and pick out the first valid for artist
|
||||
for (album in albums) {
|
||||
if (album.artist != null) {
|
||||
name = album.artist
|
||||
}
|
||||
}
|
||||
|
||||
// Also sort the mAlbums by year
|
||||
albums = albums.sortedBy { it.year }
|
||||
}
|
||||
val albums = mutableListOf<Album>()
|
||||
var numAlbums = 0
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.oxycblt.auxio.music.models
|
||||
|
||||
data class Genre(
|
||||
val id: Long,
|
||||
val name: String?
|
||||
)
|
|
@ -3,12 +3,10 @@ package org.oxycblt.auxio.music.models
|
|||
// Class containing all relevant values for a song.
|
||||
data class Song(
|
||||
val id: Long,
|
||||
val name: String?,
|
||||
val artist: String?,
|
||||
val album: String?,
|
||||
val genre: String?,
|
||||
val year: Int,
|
||||
val title: String,
|
||||
val albumName: String,
|
||||
val track: Int,
|
||||
val duration: Long,
|
||||
val coverData: ByteArray? = null
|
||||
)
|
||||
val duration: Long
|
||||
) {
|
||||
lateinit var album: Album
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue