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:
parent
6508280900
commit
f8ae5d57a5
9 changed files with 60 additions and 137 deletions
|
@ -47,10 +47,18 @@ dependencies {
|
|||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1'
|
||||
|
||||
// Navigation
|
||||
def navigation_version = "2.3.0"
|
||||
implementation "androidx.navigation:navigation-fragment-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
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||
|
||||
|
|
|
@ -72,13 +72,13 @@ class LoadingFragment : Fragment() {
|
|||
// depending on which error response was given, along with a retry button
|
||||
|
||||
binding.loadingBar.visibility = View.GONE
|
||||
binding.statusText.visibility = View.VISIBLE
|
||||
binding.errorText.visibility = View.VISIBLE
|
||||
binding.resetButton.visibility = View.VISIBLE
|
||||
|
||||
if (response == MusicLoaderResponse.NO_MUSIC) {
|
||||
binding.statusText.text = getString(R.string.error_no_music)
|
||||
binding.errorText.text = getString(R.string.error_no_music)
|
||||
} 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) {
|
||||
if (retry) {
|
||||
binding.loadingBar.visibility = View.VISIBLE
|
||||
binding.statusText.visibility = View.GONE
|
||||
binding.errorText.visibility = View.GONE
|
||||
binding.resetButton.visibility = View.GONE
|
||||
|
||||
loadingModel.doneWithRetry()
|
||||
|
|
|
@ -2,9 +2,7 @@ package org.oxycblt.auxio.music
|
|||
|
||||
import android.app.Application
|
||||
import android.content.ContentResolver
|
||||
import android.content.ContentUris
|
||||
import android.database.Cursor
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Audio.AudioColumns
|
||||
import android.util.Log
|
||||
|
@ -15,12 +13,11 @@ enum class MusicLoaderResponse {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
var songs = mutableListOf<Song>()
|
||||
|
||||
private val retriever: MediaMetadataRetriever = MediaMetadataRetriever()
|
||||
private var musicCursor: Cursor? = null
|
||||
|
||||
val response: MusicLoaderResponse
|
||||
|
@ -29,7 +26,7 @@ class MusicLoader(private val app: Application) {
|
|||
response = findMusic()
|
||||
}
|
||||
|
||||
private fun findMusic() : MusicLoaderResponse {
|
||||
private fun findMusic(): MusicLoaderResponse {
|
||||
try {
|
||||
musicCursor = getCursor(
|
||||
app.contentResolver
|
||||
|
@ -38,14 +35,11 @@ class MusicLoader(private val app: Application) {
|
|||
Log.i(this::class.simpleName, "Starting music search...")
|
||||
|
||||
useCursor()
|
||||
|
||||
} catch (error: Exception) {
|
||||
// TODO: Add better error handling
|
||||
|
||||
Log.e(this::class.simpleName, "Something went horribly wrong.")
|
||||
error.printStackTrace()
|
||||
|
||||
finalize()
|
||||
musicCursor?.close()
|
||||
|
||||
return MusicLoaderResponse.FAILURE
|
||||
}
|
||||
|
@ -72,11 +66,20 @@ class MusicLoader(private val app: Application) {
|
|||
private fun getCursor(resolver: ContentResolver): 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(
|
||||
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
|
||||
arrayOf(
|
||||
AudioColumns._ID,
|
||||
AudioColumns.DISPLAY_NAME
|
||||
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
|
||||
|
@ -94,81 +97,35 @@ class MusicLoader(private val app: Application) {
|
|||
|
||||
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)
|
||||
|
||||
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 basic metadata from the cursor
|
||||
val title = cursor.getString(titleIndex) ?: cursor.getString(displayIndex)
|
||||
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)
|
||||
|
||||
// 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,
|
||||
// so assert it as such.
|
||||
val duration = retriever.extractMetadata(
|
||||
MediaMetadataRetriever.METADATA_KEY_DURATION
|
||||
)!!.toLong()
|
||||
|
||||
// TODO: Add int-based genre compatibility
|
||||
// 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(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
genre,
|
||||
year,
|
||||
track,
|
||||
duration,
|
||||
|
||||
retriever.embeddedPicture,
|
||||
id
|
||||
|
||||
id, title, artist, album,
|
||||
year, track, duration
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the metadata retriever & the musicCursor, and make the song list immutable.
|
||||
private fun finalize() {
|
||||
retriever.close()
|
||||
musicCursor?.use{
|
||||
it.close()
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
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
|
||||
|
||||
|
||||
// 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
|
||||
// excluding the ID, as that's guaranteed to be unique [I think]
|
||||
return songs.distinctBy {
|
||||
|
@ -16,7 +14,7 @@ fun processSongs(songs: MutableList<Song>) : MutableList<Song> {
|
|||
}
|
||||
|
||||
// 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 albumList = mutableListOf<Album>()
|
||||
|
||||
|
@ -33,7 +31,7 @@ fun sortIntoAlbums(songs: MutableList<Song>) : MutableList<Album> {
|
|||
}
|
||||
|
||||
// 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 artistList = mutableListOf<Artist>()
|
||||
|
||||
|
@ -48,4 +46,4 @@ fun sortIntoArtists(albums: MutableList<Album>) : MutableList<Artist> {
|
|||
}
|
||||
|
||||
return artistList
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
package org.oxycblt.auxio.music.models
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
// Abstraction for Song
|
||||
data class Album(
|
||||
var songs: List<Song>
|
||||
) {
|
||||
var title: String? = null
|
||||
var artist: String? = null
|
||||
var genre: String? = null
|
||||
var cover: Bitmap? = null
|
||||
var year: Int = 0
|
||||
|
||||
init {
|
||||
// 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) {
|
||||
if (song.album != null) {
|
||||
title = song.album
|
||||
|
@ -24,14 +20,6 @@ data class Album(
|
|||
artist = song.artist
|
||||
}
|
||||
|
||||
if (song.genre != null) {
|
||||
genre = song.genre
|
||||
}
|
||||
|
||||
if (song.cover != null) {
|
||||
cover = song.cover
|
||||
}
|
||||
|
||||
if (song.year != 0) {
|
||||
year = song.year
|
||||
}
|
||||
|
|
|
@ -5,21 +5,13 @@ data class Artist(
|
|||
private var albums: List<Album>
|
||||
) {
|
||||
var name: String? = null
|
||||
var genre: String? = null
|
||||
|
||||
// TODO: Artist photos
|
||||
|
||||
init {
|
||||
// Like Album, iterate through the child albums and pick out the first valid
|
||||
// tag for Album/Genre
|
||||
// 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
|
||||
}
|
||||
|
||||
if (album.genre != null) {
|
||||
genre = album.genre
|
||||
}
|
||||
}
|
||||
|
||||
// Also sort the mAlbums by year
|
||||
|
|
|
@ -1,32 +1,13 @@
|
|||
package org.oxycblt.auxio.music.models
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
|
||||
// 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 track: Int,
|
||||
val duration: 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val coverData: ByteArray? = null
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
android:indeterminateTint="?attr/colorAccent"
|
||||
android:indeterminateTintMode="src_in"
|
||||
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_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -29,15 +29,15 @@
|
|||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_text"
|
||||
android:id="@+id/error_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/status_loading_music"
|
||||
app:layout_constraintBottom_toTopOf="@+id/reset_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
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
|
||||
android:id="@+id/reset_button"
|
||||
|
@ -51,7 +51,8 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
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>
|
||||
</layout>
|
|
@ -2,8 +2,6 @@
|
|||
<resources>
|
||||
<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_music_load_failed">Music loading failed.</string>
|
||||
|
||||
|
|
Loading…
Reference in a new issue