music: re-add mediastore genre parsing
Re-add MediaStore genre parsing to the init() step. This time, to play along with the new abstraction, the query is done in initialization and loaded into a map that is then used when populating raw audio.
This commit is contained in:
parent
06d6495dcd
commit
189f712eaa
2 changed files with 58 additions and 2 deletions
|
@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.directoryCompat
|
||||||
import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat
|
import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat
|
||||||
import org.oxycblt.auxio.music.queryCursor
|
import org.oxycblt.auxio.music.queryCursor
|
||||||
import org.oxycblt.auxio.music.storageVolumesCompat
|
import org.oxycblt.auxio.music.storageVolumesCompat
|
||||||
|
import org.oxycblt.auxio.music.useQuery
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.contentResolverSafe
|
import org.oxycblt.auxio.util.contentResolverSafe
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
|
@ -120,6 +121,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
private var albumArtistIndex = -1
|
private var albumArtistIndex = -1
|
||||||
|
|
||||||
private val settings = Settings(context)
|
private val settings = Settings(context)
|
||||||
|
private val genreNamesMap = mutableMapOf<Long, List<String>>()
|
||||||
|
|
||||||
private val _volumes = mutableListOf<StorageVolume>()
|
private val _volumes = mutableListOf<StorageVolume>()
|
||||||
protected val volumes: List<StorageVolume>
|
protected val volumes: List<StorageVolume>
|
||||||
|
@ -127,6 +129,9 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
|
|
||||||
/** Initialize this instance by making a query over the media database. */
|
/** Initialize this instance by making a query over the media database. */
|
||||||
open fun init(): Cursor {
|
open fun init(): Cursor {
|
||||||
|
logD("Initializing")
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
cacheLayer.init()
|
cacheLayer.init()
|
||||||
|
|
||||||
val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
val storageManager = context.getSystemServiceCompat(StorageManager::class)
|
||||||
|
@ -163,7 +168,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
selector += ')'
|
selector += ')'
|
||||||
}
|
}
|
||||||
|
|
||||||
logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]")
|
logD("Starting song query [proj: ${projection.toList()}, selector: $selector, args: $args]")
|
||||||
|
|
||||||
val cursor =
|
val cursor =
|
||||||
requireNotNull(
|
requireNotNull(
|
||||||
|
@ -191,6 +196,46 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
|
artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
|
||||||
albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
|
albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
|
||||||
|
|
||||||
|
logD("Song query succeeded [Projected total: ${cursor.count}]")
|
||||||
|
|
||||||
|
logD("Assembling genre map")
|
||||||
|
|
||||||
|
// Since we can't obtain the genre tag from a song query, we must construct
|
||||||
|
// our own equivalent from genre database queries. Theoretically, this isn't
|
||||||
|
// needed since MetadataLayer will fill this in for us, but there are some
|
||||||
|
// obscure formats where genre support is only really covered by this.
|
||||||
|
context.contentResolverSafe.useQuery(
|
||||||
|
MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
|
||||||
|
arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME)
|
||||||
|
) { genreCursor ->
|
||||||
|
val idIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
|
||||||
|
val nameIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
|
||||||
|
|
||||||
|
while (genreCursor.moveToNext()) {
|
||||||
|
// We can't assume what format these genres are derived from, so we just have
|
||||||
|
// to assume ID3v2 rules.
|
||||||
|
val id = genreCursor.getLong(idIndex)
|
||||||
|
val names = (genreCursor.getStringOrNull(nameIndex) ?: continue)
|
||||||
|
.parseId3GenreNames(settings)
|
||||||
|
|
||||||
|
context.contentResolverSafe.useQuery(
|
||||||
|
MediaStore.Audio.Genres.Members.getContentUri(VOLUME_EXTERNAL, id),
|
||||||
|
arrayOf(MediaStore.Audio.Genres.Members._ID)
|
||||||
|
) { cursor ->
|
||||||
|
val songIdIndex =
|
||||||
|
cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
// Assume that a song can't inhabit multiple genre entries, as I doubt
|
||||||
|
// Android is smart enough to separate genres into separators.
|
||||||
|
genreNamesMap[cursor.getLong(songIdIndex)] = names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logD("Finished initialization in ${System.currentTimeMillis() - start}ms")
|
||||||
|
|
||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +266,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
raw.dateModified = cursor.getLong(dateAddedIndex)
|
raw.dateModified = cursor.getLong(dateAddedIndex)
|
||||||
|
|
||||||
if (cacheLayer.maybePopulateCachedRaw(raw)) {
|
if (cacheLayer.maybePopulateCachedRaw(raw)) {
|
||||||
// We found a valid cache entry, no need to build it.
|
// We found a valid cache entry, no need to extract metadata.
|
||||||
logD("Found cached raw: ${raw.name}")
|
logD("Found cached raw: ${raw.name}")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -301,6 +346,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
// The album artist field is nullable and never has placeholder values.
|
// The album artist field is nullable and never has placeholder values.
|
||||||
raw.albumArtistNames =
|
raw.albumArtistNames =
|
||||||
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
|
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
|
||||||
|
|
||||||
|
raw.genreNames = genreNamesMap[raw.mediaStoreId]
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -313,6 +360,13 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
|
||||||
@Suppress("InlinedApi")
|
@Suppress("InlinedApi")
|
||||||
private const val AUDIO_COLUMN_ALBUM_ARTIST = MediaStore.Audio.AudioColumns.ALBUM_ARTIST
|
private const val AUDIO_COLUMN_ALBUM_ARTIST = MediaStore.Audio.AudioColumns.ALBUM_ARTIST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External has existed since at least API 21, but no constant existed for it until API 29.
|
||||||
|
* This constant is safe to use.
|
||||||
|
*/
|
||||||
|
@Suppress("InlinedApi")
|
||||||
|
private const val VOLUME_EXTERNAL = MediaStore.VOLUME_EXTERNAL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base selector that works across all versions of android. Does not exclude
|
* The base selector that works across all versions of android. Does not exclude
|
||||||
* directories.
|
* directories.
|
||||||
|
|
|
@ -62,6 +62,8 @@ import org.oxycblt.auxio.util.logW
|
||||||
* directly work with music loading, making such redundant.
|
* directly work with music loading, making such redundant.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
*
|
||||||
|
* TODO: Try to replace TaskGuard with yield when possible
|
||||||
*/
|
*/
|
||||||
class Indexer {
|
class Indexer {
|
||||||
private var lastResponse: Response? = null
|
private var lastResponse: Response? = null
|
||||||
|
|
Loading…
Reference in a new issue