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.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"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue