Remove cover/genre loading

Remove the cover loading [For now] and permanently remove the genre loading, as both take too long to do in the loading screen. Will re-add album loading once more UI is added.
This commit is contained in:
OxygenCobalt 2020-08-19 16:38:43 -06:00
parent 6508280900
commit f8ae5d57a5
9 changed files with 60 additions and 137 deletions

View file

@ -47,10 +47,18 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
// Navigation
def navigation_version = "2.3.0" def navigation_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version" implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version" implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
// Room Database
def room_version = "2.2.5"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-runtime:$room_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
// Lifecycle // Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"

View file

@ -72,13 +72,13 @@ class LoadingFragment : Fragment() {
// depending on which error response was given, along with a retry button // depending on which error response was given, along with a retry button
binding.loadingBar.visibility = View.GONE binding.loadingBar.visibility = View.GONE
binding.statusText.visibility = View.VISIBLE binding.errorText.visibility = View.VISIBLE
binding.resetButton.visibility = View.VISIBLE binding.resetButton.visibility = View.VISIBLE
if (response == MusicLoaderResponse.NO_MUSIC) { if (response == MusicLoaderResponse.NO_MUSIC) {
binding.statusText.text = getString(R.string.error_no_music) binding.errorText.text = getString(R.string.error_no_music)
} else { } else {
binding.statusText.text = getString(R.string.error_music_load_failed) binding.errorText.text = getString(R.string.error_music_load_failed)
} }
} }
@ -89,7 +89,7 @@ class LoadingFragment : Fragment() {
private fun onRetry(retry: Boolean) { private fun onRetry(retry: Boolean) {
if (retry) { if (retry) {
binding.loadingBar.visibility = View.VISIBLE binding.loadingBar.visibility = View.VISIBLE
binding.statusText.visibility = View.GONE binding.errorText.visibility = View.GONE
binding.resetButton.visibility = View.GONE binding.resetButton.visibility = View.GONE
loadingModel.doneWithRetry() loadingModel.doneWithRetry()

View file

@ -2,9 +2,7 @@ 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.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
@ -15,12 +13,11 @@ enum class MusicLoaderResponse {
} }
// Class that loads music from the FileSystem. // Class that loads music from the FileSystem.
// This thing is probably full of memory leaks. // FIXME: This thing probably has some memory leaks *somewhere*
class MusicLoader(private val app: Application) { class MusicLoader(private val app: Application) {
var songs = mutableListOf<Song>() var songs = mutableListOf<Song>()
private val retriever: MediaMetadataRetriever = MediaMetadataRetriever()
private var musicCursor: Cursor? = null private var musicCursor: Cursor? = null
val response: MusicLoaderResponse val response: MusicLoaderResponse
@ -29,7 +26,7 @@ class MusicLoader(private val app: Application) {
response = findMusic() response = findMusic()
} }
private fun findMusic() : MusicLoaderResponse { private fun findMusic(): MusicLoaderResponse {
try { try {
musicCursor = getCursor( musicCursor = getCursor(
app.contentResolver app.contentResolver
@ -38,14 +35,11 @@ class MusicLoader(private val app: Application) {
Log.i(this::class.simpleName, "Starting music search...") Log.i(this::class.simpleName, "Starting music search...")
useCursor() useCursor()
} catch (error: Exception) { } catch (error: Exception) {
// TODO: Add better error handling
Log.e(this::class.simpleName, "Something went horribly wrong.") Log.e(this::class.simpleName, "Something went horribly wrong.")
error.printStackTrace() error.printStackTrace()
finalize() musicCursor?.close()
return MusicLoaderResponse.FAILURE return MusicLoaderResponse.FAILURE
} }
@ -72,11 +66,20 @@ class MusicLoader(private val app: Application) {
private fun getCursor(resolver: ContentResolver): Cursor? { private fun getCursor(resolver: ContentResolver): Cursor? {
Log.i(this::class.simpleName, "Getting music 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( return resolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
AudioColumns._ID, AudioColumns._ID, // 0
AudioColumns.DISPLAY_NAME 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, AudioColumns.IS_MUSIC + "=1", null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER MediaStore.Audio.Media.DEFAULT_SORT_ORDER
@ -94,81 +97,35 @@ class MusicLoader(private val app: Application) {
val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID) val idIndex = cursor.getColumnIndexOrThrow(AudioColumns._ID)
val displayIndex = cursor.getColumnIndexOrThrow(AudioColumns.DISPLAY_NAME) 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)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex) val id = cursor.getLong(idIndex)
// Read the current file from the ID // Get the basic metadata from the cursor
retriever.setDataSource( val title = cursor.getString(titleIndex) ?: cursor.getString(displayIndex)
app.applicationContext, val artist = cursor.getString(artistIndex)
ContentUris.withAppendedId( val album = cursor.getString(albumIndex)
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, val year = cursor.getInt(yearIndex)
id val track = cursor.getInt(trackIndex)
) val duration = cursor.getLong(durationIndex)
)
// Get the metadata attributes // TODO: Add album art [But its loaded separately, as that will take a bit]
val title = retriever.extractMetadata( // TODO: Add genres whenever android hasn't borked it
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,
// so assert it as such.
val duration = retriever.extractMetadata(
MediaMetadataRetriever.METADATA_KEY_DURATION
)!!.toLong()
// TODO: Add int-based genre compatibility
songs.add( songs.add(
Song( Song(
title, id, title, artist, album,
artist, year, track, duration
album,
genre,
year,
track,
duration,
retriever.embeddedPicture,
id
) )
) )
} }
}
}
// Free the metadata retriever & the musicCursor, and make the song list immutable. cursor.close()
private fun finalize() {
retriever.close()
musicCursor?.use{
it.close()
} }
} }
} }

View file

@ -1,13 +1,11 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.util.Log
import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist import org.oxycblt.auxio.music.models.Artist
import org.oxycblt.auxio.music.models.Song import org.oxycblt.auxio.music.models.Song
// Sort a list of Song objects into lists of songs, albums, and artists. // Sort a list of Song objects into lists of songs, albums, and artists.
fun processSongs(songs: MutableList<Song>) : MutableList<Song> { fun processSongs(songs: MutableList<Song>): MutableList<Song> {
// Eliminate all duplicates from the list // Eliminate all duplicates from the list
// excluding the ID, as that's guaranteed to be unique [I think] // excluding the ID, as that's guaranteed to be unique [I think]
return songs.distinctBy { return songs.distinctBy {
@ -16,7 +14,7 @@ fun processSongs(songs: MutableList<Song>) : MutableList<Song> {
} }
// Sort a list of song objects into albums // Sort a list of song objects into albums
fun sortIntoAlbums(songs: MutableList<Song>) : MutableList<Album> { fun sortIntoAlbums(songs: MutableList<Song>): MutableList<Album> {
val songsByAlbum = songs.groupBy { it.album } val songsByAlbum = songs.groupBy { it.album }
val albumList = mutableListOf<Album>() val albumList = mutableListOf<Album>()
@ -33,7 +31,7 @@ fun sortIntoAlbums(songs: MutableList<Song>) : MutableList<Album> {
} }
// Sort a list of album objects into artists // Sort a list of album objects into artists
fun sortIntoArtists(albums: MutableList<Album>) : MutableList<Artist> { fun sortIntoArtists(albums: MutableList<Album>): MutableList<Artist> {
val albumsByArtist = albums.groupBy { it.artist } val albumsByArtist = albums.groupBy { it.artist }
val artistList = mutableListOf<Artist>() val artistList = mutableListOf<Artist>()
@ -48,4 +46,4 @@ fun sortIntoArtists(albums: MutableList<Album>) : MutableList<Artist> {
} }
return artistList return artistList
} }

View file

@ -1,20 +1,16 @@
package org.oxycblt.auxio.music.models package org.oxycblt.auxio.music.models
import android.graphics.Bitmap
// Abstraction for Song // Abstraction for Song
data class Album( data class Album(
var songs: List<Song> var songs: List<Song>
) { ) {
var title: String? = null var title: String? = null
var artist: String? = null var artist: String? = null
var genre: String? = null
var cover: Bitmap? = null
var year: Int = 0 var year: Int = 0
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, Artist, Genre, Year, and Cover // for the Album Name, Artist, and Year
for (song in songs) { for (song in songs) {
if (song.album != null) { if (song.album != null) {
title = song.album title = song.album
@ -24,14 +20,6 @@ data class Album(
artist = song.artist artist = song.artist
} }
if (song.genre != null) {
genre = song.genre
}
if (song.cover != null) {
cover = song.cover
}
if (song.year != 0) { if (song.year != 0) {
year = song.year year = song.year
} }

View file

@ -5,21 +5,13 @@ data class Artist(
private var albums: List<Album> private var albums: List<Album>
) { ) {
var name: String? = null var name: String? = null
var genre: String? = null
// TODO: Artist photos
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 for artist
// tag for Album/Genre
for (album in albums) { for (album in albums) {
if (album.artist != null) { if (album.artist != null) {
name = album.artist name = album.artist
} }
if (album.genre != null) {
genre = album.genre
}
} }
// Also sort the mAlbums by year // Also sort the mAlbums by year

View file

@ -1,32 +1,13 @@
package org.oxycblt.auxio.music.models package org.oxycblt.auxio.music.models
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 id: Long,
val name: String?, val name: String?,
val artist: String?, val artist: String?,
val album: String?, val album: String?,
val genre: String?,
val year: Int, val year: Int,
val track: Int, val track: Int,
val duration: Long, val duration: Long,
val coverData: ByteArray? = null
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
)
}
}
}

View file

@ -21,7 +21,7 @@
android:indeterminateTint="?attr/colorAccent" android:indeterminateTint="?attr/colorAccent"
android:indeterminateTintMode="src_in" android:indeterminateTintMode="src_in"
android:paddingBottom="@dimen/padding_small" android:paddingBottom="@dimen/padding_small"
app:layout_constraintBottom_toTopOf="@+id/status_text" app:layout_constraintBottom_toTopOf="@+id/error_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -29,15 +29,15 @@
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
<TextView <TextView
android:id="@+id/status_text" android:id="@+id/error_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/status_loading_music"
app:layout_constraintBottom_toTopOf="@+id/reset_button" app:layout_constraintBottom_toTopOf="@+id/reset_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loading_bar" /> app:layout_constraintTop_toBottomOf="@+id/loading_bar"
tools:text="@string/error_music_load_failed" />
<Button <Button
android:id="@+id/reset_button" android:id="@+id/reset_button"
@ -51,7 +51,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_text" /> app:layout_constraintTop_toBottomOf="@+id/error_text"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</layout> </layout>

View file

@ -2,8 +2,6 @@
<resources> <resources>
<string name="app_name">Auxio</string> <string name="app_name">Auxio</string>
<string name="status_loading_music">Scanning your music library for the first time...</string>
<string name="error_no_music">No music found.</string> <string name="error_no_music">No music found.</string>
<string name="error_music_load_failed">Music loading failed.</string> <string name="error_music_load_failed">Music loading failed.</string>