Heavily rewrite/refactor MusicRepository

Rewrite MusicRepository to perform all metadata calculations by themselves, also adding support for Genre loading & Album Art.
This commit is contained in:
OxygenCobalt 2020-08-18 15:22:12 -06:00
parent a8b368b577
commit 21a110edb0
4 changed files with 126 additions and 74 deletions

View file

@ -1,46 +1,43 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.graphics.Bitmap
// Basic Abstraction for Song // Basic Abstraction for Song
data class Album ( data class Album(
var mSongs: List<Song> var songs: List<Song>
) { ) {
private var mTitle: String? = null var title: String? = null
private var mArtist: String? = null var artist: String? = null
//private var mGenre: String? = null var genre: String? = null
private var mYear: Int = 0 var cover: Bitmap? = null
var year: Int = 0
// Immutable backings as the member variables are mutable
val title: String? get() = mTitle
val artist: String? get() = mArtist
//val genre: String? get() = genre
val year: Int get() = mYear
val songs: List<Song> get() = mSongs
init { init {
// Iterate through the child songs and inherit the first valid value // Iterate through the child songs and inherit the first valid value
// for the Album name & year, otherwise it will revert to its defaults // for the Album Name, Artist, Genre, Year, and Cover
for (song in mSongs) { for (song in songs) {
if (song.album != null) { if (song.album != null) {
mTitle = song.album title = song.album
} }
if (song.artist != null) { if (song.artist != null) {
mArtist = song.artist artist = song.artist
} }
/*
if (song.genre != null) { if (song.genre != null) {
mGenre = song.genre genre = song.genre
}
if (song.cover != null) {
cover = song.cover
} }
*/
if (song.year != 0) { if (song.year != 0) {
mYear = song.year year = song.year
} }
} }
// Also sort the songs by track // Also sort the songs by track
mSongs = songs.sortedBy { it.track } songs = songs.sortedBy { it.track }
} }
} }

View file

@ -2,33 +2,25 @@ package org.oxycblt.auxio.music
// Abstraction for mAlbums // Abstraction for mAlbums
data class Artist( data class Artist(
private var mAlbums: List<Album> private var albums: List<Album>
) { ) {
private var mName: String? = null var name: String? = null
//private var mGenre: String? = null var genre: String? = null
// Immutable backings as the member variables are mutable
val name: String? get() = mName
//val genre: String? get() = mGenre
val albums: List<Album> get() = mAlbums
init { init {
// Like album, iterate through the child albums and pick out the first valid // Like Album, iterate through the child albums and pick out the first valid
// tag for Album/Genre // tag for Album/Genre
for (album in mAlbums) { for (album in albums) {
if (album.artist != null) { if (album.artist != null) {
mName = album.artist name = album.artist
} }
/*
if (album.genre != null) { if (album.genre != null) {
mGenre = album.genre genre = album.genre
} }
*/
} }
// Also sort the mAlbums by year // Also sort the mAlbums by year
mAlbums = mAlbums.sortedBy { it.year } albums = albums.sortedBy { it.year }
} }
} }

View file

@ -2,7 +2,10 @@ package org.oxycblt.auxio.music
import android.app.Application import android.app.Application
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentUris
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.MediaStore.Audio.AudioColumns import android.provider.MediaStore.Audio.AudioColumns
import android.util.Log import android.util.Log
@ -39,6 +42,7 @@ class MusicRepository {
val songList = mutableListOf<Song>() val songList = mutableListOf<Song>()
try { try {
val musicCursor = getCursor( val musicCursor = getCursor(
app.contentResolver app.contentResolver
) )
@ -46,27 +50,75 @@ class MusicRepository {
// Index music files from shared storage // Index music files from shared storage
musicCursor?.use { cursor -> musicCursor?.use { cursor ->
val nameIndex = 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)
val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID) val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID)
val displayIndex = cursor.getColumnIndexOrThrow(AudioColumns.DISPLAY_NAME)
var retriever = MediaMetadataRetriever()
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex)
// Read the current file from the ID
retriever.setDataSource(
app.applicationContext,
ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
id
)
)
// Get the metadata attributes
val title = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_TITLE
) ?: cursor.getString(displayIndex)
val artist = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_ARTIST
)
val album = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_ALBUM
)
val genre = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_GENRE
)
val year = (retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_YEAR
) ?: "0").toInt()
// Track is formatted as X/0, so trim off the /0 part to parse
// the track number correctly.
val track = (retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER
) ?: "0/0").split("/")[0].toInt()
// Something has gone horribly wrong if a file has no duration.
val duration = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION
)!!.toLong()
// TODO: Add int-based genre compatibility
songList.add( songList.add(
Song( Song(
cursor.getString(nameIndex), title,
cursor.getString(artistIndex), artist,
cursor.getString(albumIndex), album,
cursor.getInt(yearIndex), genre,
cursor.getInt(trackIndex), year,
cursor.getLong(durationIndex), track,
cursor.getLong(idIndex) duration,
retriever.embeddedPicture,
id
) )
) )
} }
// Close the retriever when done so that it gets garbage collected
retriever.close()
} }
Log.d( Log.d(
@ -75,6 +127,7 @@ class MusicRepository {
) )
return songList return songList
} catch (error: Exception) { } catch (error: Exception) {
// TODO: Add better error handling // TODO: Add better error handling
@ -91,20 +144,12 @@ class MusicRepository {
return resolver.query( return resolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
AudioColumns.TITLE, AudioColumns._ID,
AudioColumns.ARTIST, AudioColumns.DISPLAY_NAME
AudioColumns.ALBUM,
AudioColumns.YEAR,
AudioColumns.TRACK,
AudioColumns.DURATION,
AudioColumns._ID
), ),
AudioColumns.IS_MUSIC + "=1", null, AudioColumns.IS_MUSIC + "=1", null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER MediaStore.Audio.Media.DEFAULT_SORT_ORDER
) )
// TODO: Art Loading, since android cant do it on its own
// TODO: Genre Loading?
} }
// Sort the list of Song objects into an abstracted lis // Sort the list of Song objects into an abstracted lis
@ -145,18 +190,18 @@ class MusicRepository {
} }
} }
Log.i(this::class.simpleName, Log.i(
"Successfully sorted songs into " this::class.simpleName,
+ artistList.size.toString() "Successfully sorted songs into " +
+ " Artists and " artistList.size.toString() +
+ albumList.size.toString() " Artists and " +
+ " Albums." albumList.size.toString() +
" Albums."
) )
mArtists = artistList mArtists = artistList
mAlbums = albumList mAlbums = albumList
mSongs = distinctSongs mSongs = distinctSongs
} }
companion object { companion object {

View file

@ -1,14 +1,32 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.graphics.Bitmap
import android.graphics.BitmapFactory
// Class containing all relevant values for a song. // Class containing all relevant values for a song.
data class Song( data class Song(
val name: String?, val name: String?,
val artist: String?, val artist: String?,
val album: String?, val album: String?,
//val genre: String?, val genre: String?,
val year: Int, val year: Int,
val track: Int, val track: Int,
val duration: Long, val duration: Long,
val id: Long?
)
private val coverData: ByteArray?,
val id: Long
) {
var cover: Bitmap? = null
init {
coverData?.let { data ->
// Decode the Album Cover ByteArray if not null.
val options = BitmapFactory.Options()
options.inMutable = true
cover = BitmapFactory.decodeByteArray(
data, 0, data.size, options
)
}
}
}