diff --git a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
index 7ba606a7c..418083170 100644
--- a/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
+++ b/app/src/main/java/org/oxycblt/auxio/coil/Fetchers.kt
@@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
+import org.oxycblt.auxio.ui.Sort
import kotlin.math.min
/**
@@ -76,7 +77,10 @@ class ArtistImageFetcher private constructor(
private val artist: Artist,
) : AuxioFetcher() {
override suspend fun fetch(): FetchResult? {
- val results = artist.albums.mapAtMost(4) { album ->
+ val albums = Sort.ByName(true)
+ .sortAlbums(artist.albums)
+
+ val results = albums.mapAtMost(4) { album ->
fetchArt(context, album)
}
@@ -100,6 +104,7 @@ class GenreImageFetcher private constructor(
private val genre: Genre,
) : AuxioFetcher() {
override suspend fun fetch(): FetchResult? {
+ // We don't need to sort here, as the way we
val albums = genre.songs.groupBy { it.album }.keys
val results = albums.mapAtMost(4) { album ->
fetchArt(context, album)
diff --git a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt
index 2e913ec6f..7e3671af1 100644
--- a/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt
+++ b/app/src/main/java/org/oxycblt/auxio/excluded/ExcludedDialog.kt
@@ -18,7 +18,6 @@
package org.oxycblt.auxio.excluded
-import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
@@ -32,14 +31,13 @@ import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import org.oxycblt.auxio.BuildConfig
-import org.oxycblt.auxio.MainActivity
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogExcludedBinding
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.LifecycleDialog
+import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
-import kotlin.system.exitProcess
/**
* Dialog that manages the currently excluded directories.
@@ -150,25 +148,12 @@ class ExcludedDialog : LifecycleDialog() {
private fun saveAndRestart() {
excludedModel.save {
- playbackModel.savePlaybackState(requireContext(), ::hardRestart)
+ playbackModel.savePlaybackState(requireContext()) {
+ requireContext().hardRestart()
+ }
}
}
- private fun hardRestart() {
- logD("Performing hard restart.")
-
- // Instead of having to do a ton of cleanup and horrible code changes
- // to restart this application non-destructively, I just restart the UI task [There is only
- // one, after all] and then kill the application using exitProcess. Works well enough.
- val intent = Intent(requireContext().applicationContext, MainActivity::class.java).setFlags(
- Intent.FLAG_ACTIVITY_CLEAR_TASK
- )
-
- startActivity(intent)
-
- exitProcess(0)
- }
-
/**
* Get *just* the root path, nothing else is really needed.
*/
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
index 947343188..db3f11c2c 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt
@@ -1,37 +1,14 @@
-/*
- * Copyright (c) 2021 Auxio Project
- * MusicLoader.kt is part of Auxio.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
package org.oxycblt.auxio.music
-import android.annotation.SuppressLint
import android.content.Context
import android.provider.MediaStore
-import android.provider.MediaStore.Audio.Genres
-import android.provider.MediaStore.Audio.Media
import androidx.core.database.getStringOrNull
import org.oxycblt.auxio.R
import org.oxycblt.auxio.excluded.ExcludedDatabase
-import org.oxycblt.auxio.ui.Sort
-import org.oxycblt.auxio.util.logD
/**
- * This class does pretty much all the black magic required to get a remotely sensible music
- * indexing system while still optimizing for time. I would recommend you leave this file now
+ * This class acts as the base for most the black magic required to get a remotely sensible music
+ * indexing system while still optimizing for time. I would recommend you leave this module now
* before you lose your sanity trying to understand the hoops I had to jump through for this
* system, but if you really want to stay, here's a debrief on why this code is so awful.
*
@@ -90,111 +67,72 @@ import org.oxycblt.auxio.util.logD
*
* @author OxygenCobalt
*/
-class MusicLoader(private val context: Context) {
- var genres = mutableListOf()
- var albums = mutableListOf()
- var artists = mutableListOf()
- var songs = mutableListOf()
+class MusicLoader {
+ data class Library(
+ val genres: List,
+ val artists: List,
+ val albums: List,
+ val songs: List
+ )
- private val resolver = context.contentResolver
+ fun load(context: Context): Library? {
+ val songs = loadSongs(context)
+ if (songs.isEmpty()) return null
- private var selector = "${Media.IS_MUSIC}=1"
- private var args = arrayOf()
+ val albums = buildAlbums(songs)
+ val artists = buildArtists(context, albums)
+ val genres = readGenres(context, songs)
- /**
- * Begin the loading process.
- * Resulting models are pushed to [genres], [artists], [albums], and [songs].
- */
- fun load() {
- buildSelector()
-
- loadGenres()
- loadSongs()
- linkGenres()
-
- buildAlbums()
- buildArtists()
+ return Library(
+ genres,
+ artists,
+ albums,
+ songs
+ )
}
- @Suppress("DEPRECATION")
- private fun buildSelector() {
+ private fun loadSongs(context: Context): List {
+ var songs = mutableListOf()
val blacklistDatabase = ExcludedDatabase.getInstance(context)
-
val paths = blacklistDatabase.readPaths()
- // DATA was deprecated on Android Q, but is set to be un-deprecated in Android 12L
+ var selector = "${MediaStore.Audio.Media.IS_MUSIC}=1"
+ val args = mutableListOf()
+
+ // DATA was deprecated on Android 10, but is set to be un-deprecated in Android 12L.
// The only reason we'd want to change this is to add external partitions support, but
// that's less efficent and there's no demand for that right now.
for (path in paths) {
- selector += " AND ${Media.DATA} NOT LIKE ?"
+ selector += " AND ${MediaStore.Audio.Media.DATA} NOT LIKE ?"
args += "$path%" // Append % so that the selector properly detects children
}
- }
- private fun loadGenres() {
- logD("Starting genre search...")
-
- // First, get a cursor for every genre in the android system
- val genreCursor = resolver.query(
- Genres.EXTERNAL_CONTENT_URI,
+ context.contentResolver.query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf(
- Genres._ID, // 0
- Genres.NAME // 1
+ MediaStore.Audio.Media._ID,
+ MediaStore.Audio.Media.TITLE,
+ MediaStore.Audio.Media.DISPLAY_NAME,
+ MediaStore.Audio.Media.ALBUM,
+ MediaStore.Audio.Media.ALBUM_ID,
+ MediaStore.Audio.Media.ARTIST,
+ MediaStore.Audio.Media.ALBUM_ARTIST,
+ MediaStore.Audio.Media.YEAR,
+ MediaStore.Audio.Media.TRACK,
+ MediaStore.Audio.Media.DURATION,
),
- null, null,
- Genres.DEFAULT_SORT_ORDER
- )
-
- // And then process those into Genre objects
- genreCursor?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(Genres._ID)
- val nameIndex = cursor.getColumnIndexOrThrow(Genres.NAME)
-
- while (cursor.moveToNext()) {
- val id = cursor.getLong(idIndex)
- // No non-broken genre would be missing a name.
- val name = cursor.getStringOrNull(nameIndex) ?: continue
-
- genres.add(Genre(id, name, name.getGenreNameCompat() ?: name))
- }
- }
-
- logD("Genre search finished with ${genres.size} genres found.")
- }
-
- @SuppressLint("InlinedApi")
- private fun loadSongs() {
- logD("Starting song search...")
-
- val songCursor = resolver.query(
- Media.EXTERNAL_CONTENT_URI,
- arrayOf(
- Media._ID, // 0
- Media.TITLE, // 1
- Media.DISPLAY_NAME, // 2
- Media.ALBUM, // 3
- Media.ALBUM_ID, // 4
- Media.ARTIST, // 5
- Media.ALBUM_ARTIST, // 6
- Media.YEAR, // 7
- Media.TRACK, // 8
- Media.DURATION, // 9
- ),
- selector, args,
- Media.DEFAULT_SORT_ORDER
- )
-
- songCursor?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(Media._ID)
- val titleIndex = cursor.getColumnIndexOrThrow(Media.TITLE)
- val fileIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)
- val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM)
- val albumIdIndex = cursor.getColumnIndexOrThrow(Media.ALBUM_ID)
- val artistIndex = cursor.getColumnIndexOrThrow(Media.ARTIST)
- val albumArtistIndex = cursor.getColumnIndexOrThrow(Media.ALBUM_ARTIST)
- val yearIndex = cursor.getColumnIndexOrThrow(Media.YEAR)
- val trackIndex = cursor.getColumnIndexOrThrow(Media.TRACK)
- val durationIndex = cursor.getColumnIndexOrThrow(Media.DURATION)
+ selector, args.toTypedArray(), null
+ )?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
+ val titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
+ val fileIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
+ val albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)
+ val albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)
+ val artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
+ val albumArtistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ARTIST)
+ val yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.YEAR)
+ val trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TRACK)
+ val durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex)
@@ -226,20 +164,19 @@ class MusicLoader(private val context: Context) {
it.name to it.albumName to it.artistName to it.track to it.duration
}.toMutableList()
- logD("Song search finished with ${songs.size} found")
+ return songs
}
- private fun buildAlbums() {
- logD("Linking albums")
-
+ private fun buildAlbums(songs: List): List {
// Group up songs by their album ids and then link them with their albums
+ val albums = mutableListOf()
val songsByAlbum = songs.groupBy { it.albumId }
songsByAlbum.forEach { entry ->
- // Rely on the first song in this list for album information.
- // Note: This might result in a bad year being used for an album if an album's songs
- // have multiple years. This is fixable but is currently omitted for speed.
- val song = entry.value[0]
+ // Use the song with the latest year as our metadata song.
+ // This allows us to replicate the LAST_YEAR field, which is useful as it means that
+ // weird years like "0" wont show up if there are alternatives.
+ val song = requireNotNull(entry.value.maxByOrNull { it.year })
albums.add(
Album(
@@ -253,14 +190,12 @@ class MusicLoader(private val context: Context) {
}
albums.removeAll { it.songs.isEmpty() }
- albums = Sort.ByName(true).sortAlbums(albums).toMutableList()
- logD("Songs successfully linked into ${albums.size} albums")
+ return albums
}
- private fun buildArtists() {
- logD("Linking artists")
-
+ private fun buildArtists(context: Context, albums: List): List {
+ val artists = mutableListOf()
val albumsByArtist = albums.groupBy { it.artistName }
albumsByArtist.forEach { entry ->
@@ -270,8 +205,8 @@ class MusicLoader(private val context: Context) {
entry.key
}
- // Because of our hacky album artist system, MediaStore artist IDs are unreliable.
- // Therefore we just use the hashCode of the artist name as our ID and move on.
+ // In most cases, MediaStore artist IDs are unreliable or omitted for speed.
+ // Use the hashCode of the artist name as our ID and move on.
artists.add(
Artist(
id = entry.key.hashCode().toLong(),
@@ -282,36 +217,42 @@ class MusicLoader(private val context: Context) {
)
}
- artists = Sort.ByName(true).sortParents(artists).toMutableList()
-
- logD("Albums successfully linked into ${artists.size} artists")
+ return artists
}
- private fun linkGenres() {
- logD("Linking genres")
+ private fun readGenres(context: Context, songs: List): List {
+ val genres = mutableListOf()
- genres.forEach { genre ->
- val songCursor = resolver.query(
- Genres.Members.getContentUri("external", genre.id),
- arrayOf(Genres.Members._ID),
- null, null, null // Dont even bother blacklisting here as useless iters are less expensive than IO
- )
+ // First, get a cursor for every genre in the android system
+ val genreCursor = context.contentResolver.query(
+ MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
+ arrayOf(
+ MediaStore.Audio.Genres._ID,
+ MediaStore.Audio.Genres.NAME
+ ),
+ null, null, null
+ )
- songCursor?.use { cursor ->
- val idIndex = cursor.getColumnIndexOrThrow(Genres.Members._ID)
+ // And then process those into Genre objects
+ genreCursor?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
+ val nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
- while (cursor.moveToNext()) {
- val id = cursor.getLong(idIndex)
+ while (cursor.moveToNext()) {
+ // No non-broken genre would be missing a name.
+ val id = cursor.getLong(idIndex)
+ val name = cursor.getStringOrNull(nameIndex) ?: continue
- songs.find { it.id == id }?.let { song ->
- genre.linkSong(song)
- }
- }
+ val genre = Genre(
+ id, name, name.getGenreNameCompat() ?: name
+ )
+
+ linkGenre(context, genre, songs)
+ genres.add(genre)
}
}
// Songs that don't have a genre will be thrown into an unknown genre.
-
val unknownGenre = Genre(
id = Long.MIN_VALUE,
name = MediaStore.UNKNOWN_STRING,
@@ -327,5 +268,27 @@ class MusicLoader(private val context: Context) {
if (unknownGenre.songs.isNotEmpty()) {
genres.add(unknownGenre)
}
+
+ return genres
+ }
+
+ private fun linkGenre(context: Context, genre: Genre, songs: List) {
+ val songCursor = context.contentResolver.query(
+ MediaStore.Audio.Genres.Members.getContentUri("external", genre.id),
+ arrayOf(MediaStore.Audio.Genres.Members._ID),
+ null, null, null // Dont even bother blacklisting here as useless iters are less expensive than IO
+ )
+
+ songCursor?.use { cursor ->
+ val idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
+
+ while (cursor.moveToNext()) {
+ val id = cursor.getLong(idIndex)
+
+ songs.find { it.id == id }?.let { song ->
+ genre.linkSong(song)
+ }
+ }
+ }
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
index a03d89156..be38b942a 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt
@@ -68,17 +68,13 @@ class MusicStore private constructor() {
try {
val start = System.currentTimeMillis()
- val loader = MusicLoader(context)
- loader.load()
+ val loader = MusicLoader()
+ val library = loader.load(context) ?: return Response.Err(ErrorKind.NO_MUSIC)
- if (loader.songs.isEmpty()) {
- return Response.Err(ErrorKind.NO_MUSIC)
- }
-
- mSongs = loader.songs
- mAlbums = loader.albums
- mArtists = loader.artists
- mGenres = loader.genres
+ mSongs = library.songs
+ mAlbums = library.albums
+ mArtists = library.artists
+ mGenres = library.genres
logD("Music load completed successfully in ${System.currentTimeMillis() - start}ms.")
} catch (e: Exception) {
diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
index ba55d8b9a..61e7dda93 100644
--- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
+++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt
@@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.DisplayMode
+import org.oxycblt.auxio.ui.Sort
import java.text.Normalizer
/**
@@ -77,6 +78,7 @@ class SearchViewModel : ViewModel() {
// Searching can be quite expensive, so hop on a co-routine
viewModelScope.launch {
+ val sort = Sort.ByName(true)
val results = mutableListOf()
// A filter mode of null means to not filter at all.
@@ -84,28 +86,28 @@ class SearchViewModel : ViewModel() {
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ARTISTS) {
musicStore.artists.filterByOrNull(query)?.let { artists ->
results.add(Header(-1, HeaderString.Single(R.string.lbl_artists)))
- results.addAll(artists)
+ results.addAll(sort.sortParents(artists))
}
}
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_ALBUMS) {
musicStore.albums.filterByOrNull(query)?.let { albums ->
results.add(Header(-2, HeaderString.Single(R.string.lbl_albums)))
- results.addAll(albums)
+ results.addAll(sort.sortAlbums(albums))
}
}
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_GENRES) {
musicStore.genres.filterByOrNull(query)?.let { genres ->
results.add(Header(-3, HeaderString.Single(R.string.lbl_genres)))
- results.addAll(genres)
+ results.addAll(sort.sortParents(genres))
}
}
if (mFilterMode == null || mFilterMode == DisplayMode.SHOW_SONGS) {
musicStore.songs.filterByOrNull(query)?.let { songs ->
results.add(Header(-4, HeaderString.Single(R.string.lbl_songs)))
- results.addAll(songs)
+ results.addAll(sort.sortSongs(songs))
}
}
@@ -136,7 +138,7 @@ class SearchViewModel : ViewModel() {
* Shortcut that will run a ignoreCase filter on a list and only return
* a value if the resulting list is empty.
*/
- private fun List.filterByOrNull(value: String): List? {
+ private fun List.filterByOrNull(value: String): List? {
val filtered = filter {
// Ensure the name we match with is correct.
val name = if (it is MusicParent) {
diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
index d49c8fb3a..ddbdcd723 100644
--- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
+++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt
@@ -30,6 +30,7 @@ import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import org.oxycblt.auxio.MainActivity
import kotlin.reflect.KClass
+import kotlin.system.exitProcess
const val INTENT_REQUEST_CODE = 0xA0A0
@@ -83,6 +84,19 @@ fun Context.newMainIntent(): PendingIntent {
)
}
+fun Context.hardRestart() {
+ // Instead of having to do a ton of cleanup and horrible code changes
+ // to restart this application non-destructively, I just restart the UI task [There is only
+ // one, after all] and then kill the application using exitProcess. Works well enough.
+ val intent = Intent(applicationContext, MainActivity::class.java).setFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TASK
+ )
+
+ startActivity(intent)
+
+ exitProcess(0)
+}
+
/**
* Create a toast using the provided string resource.
*/
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 9b89d707f..83b5f1c1f 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -55,7 +55,7 @@
"Tmavé"
"Barva"
"Černé téma"
- "Použít kompletně černé tmavé téma"
+ "Použít kompletně černé tmavé téma"
"Zobrazení"
"Zobrazit barvy alba"
"Vypněte pro ušetření paměti"
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 3bf2862c2..94f08a5b7 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -154,7 +154,7 @@
Album
Jahr
Schwarzes Thema
- Ein schwarzes Thema für das dunkle verwenden
+ Ein schwarzes Thema für das dunkle verwenden
Pause bei Wiederholung
Pausiert, wenn ein Song wiederholt wird
Zufällig an- oder ausschalten
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 4b2ee8607..070efaed8 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -60,7 +60,7 @@
Oscuro
Acento
Tema negro
- Usar tema negro puro
+ Usar tema negro puro
Pantalla
Mostrar carátula de álbum
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 15ea48b4c..69f56711c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -65,7 +65,7 @@
Dark
Color scheme
Black theme
- Use a pure-black dark theme
+ Use a pure-black dark theme
Display
Library tabs
diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml
index 4d4ccf6a5..f7f66db90 100644
--- a/app/src/main/res/xml/prefs_main.xml
+++ b/app/src/main/res/xml/prefs_main.xml
@@ -24,7 +24,7 @@
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="KEY_BLACK_THEME"
- app:summary="@string/setting_black_mode_desc"
+ app:summary="@string/set_black_mode_desc"
app:title="@string/set_black_mode" />
diff --git a/info/FAQ.md b/info/FAQ.md
index 294022ee3..103d3c7fe 100644
--- a/info/FAQ.md
+++ b/info/FAQ.md
@@ -16,8 +16,7 @@ This is probably caused by one of two reasons:
#### I have a large library and Auxio takes really long to load it!
-This is expected since reading media takes awhile, especially with libraries containing 10k songs or more.
-I hope to mitigate this in the future by allowing one to customize the music loader to optimize for speed instead of accuracy.
+This is expected since reading from the audio database takes awhile, especially with libraries containing 10k songs or more.
#### Why ExoPlayer?