Move playback state system to hashes
Use unique-ish hashes in the playback state system instead of the less efficent and less reliable string system. This cuts save times in ~half and improves restore times by ~1/3. Yeah, this is like the 4th time I've changed this system but unless I have some major loader refactor I think this wont change again.
This commit is contained in:
parent
969f25176a
commit
17e5aed131
12 changed files with 121 additions and 96 deletions
|
@ -6,6 +6,7 @@ import android.database.sqlite.SQLiteDatabase
|
|||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.database.sqlite.transaction
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.ui.assertBackgroundThread
|
||||
|
||||
/**
|
||||
* Database for storing blacklisted paths.
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.oxycblt.auxio.database
|
|||
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.os.Looper
|
||||
|
||||
/**
|
||||
* Shortcut for querying all items in a database and running [block] with the cursor returned.
|
||||
|
@ -10,12 +9,3 @@ import android.os.Looper
|
|||
*/
|
||||
fun <R> SQLiteDatabase.queryAll(tableName: String, block: (Cursor) -> R) =
|
||||
query(tableName, null, null, null, null, null, null)?.use(block)
|
||||
|
||||
/**
|
||||
* Assert that we are on a background thread.
|
||||
*/
|
||||
fun assertBackgroundThread() {
|
||||
check(Looper.myLooper() != Looper.getMainLooper()) {
|
||||
"Database operations must be ran on a background thread."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package org.oxycblt.auxio.database
|
|||
/**
|
||||
* A database entity that stores a compressed variant of the current playback state.
|
||||
* @property id - The database key for this state
|
||||
* @property songName - The song that is currently playing
|
||||
* @property parentName - The parent that is being played from [-1 if none]
|
||||
* @property songHash - The hash for the currently playing song
|
||||
* @property parentHash - The hash for the currently playing parent
|
||||
* @property index - The current index in the queue.
|
||||
* @property mode - The integer form of the current [org.oxycblt.auxio.playback.state.PlaybackMode]
|
||||
* @property isShuffling - A bool for if the queue was shuffled
|
||||
|
@ -14,10 +14,9 @@ package org.oxycblt.auxio.database
|
|||
*/
|
||||
data class PlaybackState(
|
||||
val id: Long = 0L,
|
||||
val songName: String = "",
|
||||
val songAlbumName: String = "",
|
||||
val songHash: Int,
|
||||
val position: Long,
|
||||
val parentName: String = "",
|
||||
val parentHash: Int,
|
||||
val index: Int,
|
||||
val mode: Int,
|
||||
val isShuffling: Boolean,
|
||||
|
@ -26,11 +25,10 @@ data class PlaybackState(
|
|||
) {
|
||||
companion object {
|
||||
const val COLUMN_ID = "state_id"
|
||||
const val COLUMN_SONG_NAME = "cur_song_name"
|
||||
const val COLUMN_SONG_ALBUM_NAME = "cur_song_album"
|
||||
const val COLUMN_SONG_HASH = "song"
|
||||
const val COLUMN_POSITION = "position"
|
||||
const val COLUMN_PARENT_NAME = "parent_name"
|
||||
const val COLUMN_INDEX = "state_index"
|
||||
const val COLUMN_PARENT_HASH = "parent"
|
||||
const val COLUMN_INDEX = "_index"
|
||||
const val COLUMN_MODE = "mode"
|
||||
const val COLUMN_IS_SHUFFLING = "is_shuffling"
|
||||
const val COLUMN_LOOP_MODE = "loop_mode"
|
||||
|
|
|
@ -4,9 +4,9 @@ import android.content.ContentValues
|
|||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.database.getStringOrNull
|
||||
import androidx.core.database.sqlite.transaction
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.ui.assertBackgroundThread
|
||||
|
||||
/**
|
||||
* A SQLite database for managing the persistent playback state and queue.
|
||||
|
@ -57,10 +57,9 @@ class PlaybackStateDatabase(context: Context) :
|
|||
*/
|
||||
private fun constructStateTable(command: StringBuilder): StringBuilder {
|
||||
command.append("${PlaybackState.COLUMN_ID} LONG PRIMARY KEY,")
|
||||
.append("${PlaybackState.COLUMN_SONG_NAME} TEXT NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_SONG_ALBUM_NAME} TEXT NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_POSITION} LONG NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_PARENT_NAME} TEXT NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_PARENT_HASH} INTEGER NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_INDEX} INTEGER NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_MODE} INTEGER NOT NULL,")
|
||||
.append("${PlaybackState.COLUMN_IS_SHUFFLING} BOOLEAN NOT NULL,")
|
||||
|
@ -75,8 +74,8 @@ class PlaybackStateDatabase(context: Context) :
|
|||
*/
|
||||
private fun constructQueueTable(command: StringBuilder): StringBuilder {
|
||||
command.append("${QueueItem.COLUMN_ID} LONG PRIMARY KEY,")
|
||||
.append("${QueueItem.COLUMN_SONG_NAME} TEXT NOT NULL,")
|
||||
.append("${QueueItem.COLUMN_ALBUM_NAME} TEXT NOT NULL,")
|
||||
.append("${QueueItem.COLUMN_SONG_HASH} INTEGER NOT NULL,")
|
||||
.append("${QueueItem.COLUMN_ALBUM_HASH} INTEGER NOT NULL,")
|
||||
.append("${QueueItem.COLUMN_IS_USER_QUEUE} BOOLEAN NOT NULL)")
|
||||
|
||||
return command
|
||||
|
@ -97,10 +96,9 @@ class PlaybackStateDatabase(context: Context) :
|
|||
|
||||
val stateData = ContentValues(10).apply {
|
||||
put(PlaybackState.COLUMN_ID, state.id)
|
||||
put(PlaybackState.COLUMN_SONG_NAME, state.songName)
|
||||
put(PlaybackState.COLUMN_SONG_ALBUM_NAME, state.songAlbumName)
|
||||
put(PlaybackState.COLUMN_SONG_HASH, state.songHash)
|
||||
put(PlaybackState.COLUMN_POSITION, state.position)
|
||||
put(PlaybackState.COLUMN_PARENT_NAME, state.parentName)
|
||||
put(PlaybackState.COLUMN_PARENT_HASH, state.parentHash)
|
||||
put(PlaybackState.COLUMN_INDEX, state.index)
|
||||
put(PlaybackState.COLUMN_MODE, state.mode)
|
||||
put(PlaybackState.COLUMN_IS_SHUFFLING, state.isShuffling)
|
||||
|
@ -126,10 +124,9 @@ class PlaybackStateDatabase(context: Context) :
|
|||
readableDatabase.queryAll(TABLE_NAME_STATE) { cursor ->
|
||||
if (cursor.count == 0) return@queryAll
|
||||
|
||||
val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_NAME)
|
||||
val albumIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_ALBUM_NAME)
|
||||
val songIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_SONG_HASH)
|
||||
val posIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_POSITION)
|
||||
val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_NAME)
|
||||
val parentIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_PARENT_HASH)
|
||||
val indexIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_INDEX)
|
||||
val modeIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_MODE)
|
||||
val shuffleIndex = cursor.getColumnIndexOrThrow(PlaybackState.COLUMN_IS_SHUFFLING)
|
||||
|
@ -141,10 +138,9 @@ class PlaybackStateDatabase(context: Context) :
|
|||
cursor.moveToFirst()
|
||||
|
||||
state = PlaybackState(
|
||||
songName = cursor.getStringOrNull(songIndex) ?: "",
|
||||
songAlbumName = cursor.getStringOrNull(albumIndex) ?: "",
|
||||
songHash = cursor.getInt(songIndex),
|
||||
position = cursor.getLong(posIndex),
|
||||
parentName = cursor.getStringOrNull(parentIndex) ?: "",
|
||||
parentHash = cursor.getInt(parentIndex),
|
||||
index = cursor.getInt(indexIndex),
|
||||
mode = cursor.getInt(modeIndex),
|
||||
isShuffling = cursor.getInt(shuffleIndex) == 1,
|
||||
|
@ -183,8 +179,8 @@ class PlaybackStateDatabase(context: Context) :
|
|||
|
||||
val itemData = ContentValues(4).apply {
|
||||
put(QueueItem.COLUMN_ID, item.id)
|
||||
put(QueueItem.COLUMN_SONG_NAME, item.songName)
|
||||
put(QueueItem.COLUMN_ALBUM_NAME, item.albumName)
|
||||
put(QueueItem.COLUMN_SONG_HASH, item.songHash)
|
||||
put(QueueItem.COLUMN_ALBUM_HASH, item.albumHash)
|
||||
put(QueueItem.COLUMN_IS_USER_QUEUE, item.isUserQueue)
|
||||
}
|
||||
|
||||
|
@ -213,15 +209,15 @@ class PlaybackStateDatabase(context: Context) :
|
|||
if (cursor.count == 0) return@queryAll
|
||||
|
||||
val idIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ID)
|
||||
val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_NAME)
|
||||
val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_NAME)
|
||||
val songIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_SONG_HASH)
|
||||
val albumIdIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_ALBUM_HASH)
|
||||
val isUserQueueIndex = cursor.getColumnIndexOrThrow(QueueItem.COLUMN_IS_USER_QUEUE)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
queueItems += QueueItem(
|
||||
id = cursor.getLong(idIndex),
|
||||
songName = cursor.getStringOrNull(songIdIndex) ?: "",
|
||||
albumName = cursor.getStringOrNull(albumIdIndex) ?: "",
|
||||
songHash = cursor.getInt(songIdIndex),
|
||||
albumHash = cursor.getInt(albumIdIndex),
|
||||
isUserQueue = cursor.getInt(isUserQueueIndex) == 1
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,21 +3,21 @@ package org.oxycblt.auxio.database
|
|||
/**
|
||||
* A database entity that stores a simplified representation of a song in a queue.
|
||||
* @property id The database entity's id
|
||||
* @property songName The song name for this queue item
|
||||
* @property albumName The album name for this queue item, used to make searching quicker
|
||||
* @property songHash The hash for the song represented
|
||||
* @property albumHash The hash for the album represented
|
||||
* @property isUserQueue A bool for if this queue item is a user queue item or not
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
data class QueueItem(
|
||||
var id: Long = 0L,
|
||||
val songName: String = "",
|
||||
val albumName: String = "",
|
||||
val songHash: Int,
|
||||
val albumHash: Int,
|
||||
val isUserQueue: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
const val COLUMN_ID = "id"
|
||||
const val COLUMN_SONG_NAME = "song_name"
|
||||
const val COLUMN_ALBUM_NAME = "album_name"
|
||||
const val COLUMN_SONG_HASH = "song"
|
||||
const val COLUMN_ALBUM_HASH = "album"
|
||||
const val COLUMN_IS_USER_QUEUE = "is_user_queue"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ import android.net.Uri
|
|||
|
||||
// --- MUSIC MODELS ---
|
||||
|
||||
// TODO: Implement some kind of hash system, removing the need to redundant names but also without the volatility of id
|
||||
// They need to be completely unique, however, and from whatever information I have about them on creation
|
||||
|
||||
/**
|
||||
* The base data object for all music.
|
||||
* @property id The ID that is assigned to this object
|
||||
|
@ -20,10 +17,18 @@ sealed class BaseModel {
|
|||
/**
|
||||
* [BaseModel] variant that denotes that this object is a parent of other data objects, such
|
||||
* as an [Album] or [Artist]
|
||||
* @property displayName Name that handles the usage of [Genre.resolvedName] and the normal [BaseModel.name]
|
||||
* @property hash A versatile, unique(ish) hash used for databases
|
||||
* @property displayName Name that handles the usage of [Genre.resolvedName]
|
||||
* and the normal [BaseModel.name]
|
||||
*/
|
||||
sealed class Parent : BaseModel() {
|
||||
val displayName: String get() = if (this is Genre) resolvedName else name
|
||||
abstract val hash: Int
|
||||
|
||||
val displayName: String get() = if (this is Genre) {
|
||||
resolvedName
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,6 +43,7 @@ sealed class Parent : BaseModel() {
|
|||
* These are not ensured to be linked due to possible quirks in the genre loading system.
|
||||
* @property seconds The Song's duration in seconds
|
||||
* @property formattedDuration The Song's duration as a duration string.
|
||||
* @property hash A versatile, unique(ish) hash used for databases
|
||||
*/
|
||||
data class Song(
|
||||
override val id: Long = -1,
|
||||
|
@ -45,7 +51,7 @@ data class Song(
|
|||
val fileName: String,
|
||||
val albumId: Long = -1,
|
||||
val track: Int = -1,
|
||||
val duration: Long = 0,
|
||||
val duration: Long = 0
|
||||
) : BaseModel() {
|
||||
private var mAlbum: Album? = null
|
||||
private var mGenre: Genre? = null
|
||||
|
@ -56,6 +62,8 @@ data class Song(
|
|||
val seconds = duration / 1000
|
||||
val formattedDuration = seconds.toDuration()
|
||||
|
||||
val hash = songHash()
|
||||
|
||||
fun linkAlbum(album: Album) {
|
||||
if (mAlbum == null) {
|
||||
mAlbum = album
|
||||
|
@ -67,6 +75,13 @@ data class Song(
|
|||
mGenre = genre
|
||||
}
|
||||
}
|
||||
|
||||
private fun songHash(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + track
|
||||
result = 31 * result + duration.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -94,6 +109,8 @@ data class Album(
|
|||
val totalDuration: String get() =
|
||||
songs.sumOf { it.seconds }.toDuration()
|
||||
|
||||
override val hash = albumHash()
|
||||
|
||||
fun linkArtist(artist: Artist) {
|
||||
mArtist = artist
|
||||
}
|
||||
|
@ -104,6 +121,13 @@ data class Album(
|
|||
mSongs.add(song)
|
||||
}
|
||||
}
|
||||
|
||||
private fun albumHash(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + artistName.hashCode()
|
||||
result = 31 * result + year
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,12 +141,6 @@ data class Artist(
|
|||
override val name: String,
|
||||
val albums: List<Album>
|
||||
) : Parent() {
|
||||
init {
|
||||
albums.forEach { album ->
|
||||
album.linkArtist(this)
|
||||
}
|
||||
}
|
||||
|
||||
val genre: Genre? by lazy {
|
||||
// Get the genre that corresponds to the most songs in this artist, which would be
|
||||
// the most "Prominent" genre.
|
||||
|
@ -132,6 +150,14 @@ data class Artist(
|
|||
val songs: List<Song> by lazy {
|
||||
albums.flatMap { it.songs }
|
||||
}
|
||||
|
||||
override val hash = name.hashCode()
|
||||
|
||||
init {
|
||||
albums.forEach { album ->
|
||||
album.linkArtist(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,6 +179,8 @@ data class Genre(
|
|||
val totalDuration: String get() =
|
||||
songs.sumOf { it.seconds }.toDuration()
|
||||
|
||||
override val hash = name.hashCode()
|
||||
|
||||
fun linkSong(song: Song) {
|
||||
mSongs.add(song)
|
||||
song.linkGenre(this)
|
||||
|
|
|
@ -13,8 +13,12 @@ import org.oxycblt.auxio.logD
|
|||
|
||||
/**
|
||||
* Class that loads/constructs [Genre]s, [Artist]s, [Album]s, and [Song] objects from the filesystem
|
||||
* TODO: Use album artist instead of artist tag.
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
* FIXME: Here's a catalog of problems that I already know about with this abomination
|
||||
* - Does not support the album artist tag
|
||||
* - All loading is done at startup [Not efficent for large libraries, would require massive arch retooling to fix]
|
||||
* - Genre system is a bottleneck [Nothing I can do about it, MediaStore is garbage]
|
||||
*/
|
||||
class MusicLoader(private val context: Context) {
|
||||
var genres = mutableListOf<Genre>()
|
||||
|
@ -94,9 +98,9 @@ class MusicLoader(private val context: Context) {
|
|||
Albums._ID, // 0
|
||||
Albums.ALBUM, // 1
|
||||
Albums.ARTIST, // 2
|
||||
Albums.FIRST_YEAR, // 3
|
||||
Albums.FIRST_YEAR, // 4
|
||||
),
|
||||
null, null,
|
||||
"", null,
|
||||
Albums.DEFAULT_SORT_ORDER
|
||||
)
|
||||
|
||||
|
@ -106,13 +110,13 @@ class MusicLoader(private val context: Context) {
|
|||
albumCursor?.use { cursor ->
|
||||
val idIndex = cursor.getColumnIndexOrThrow(Albums._ID)
|
||||
val nameIndex = cursor.getColumnIndexOrThrow(Albums.ALBUM)
|
||||
val artistIdIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST)
|
||||
val artistNameIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST)
|
||||
val yearIndex = cursor.getColumnIndexOrThrow(Albums.FIRST_YEAR)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
val id = cursor.getLong(idIndex)
|
||||
val name = cursor.getString(nameIndex) ?: albumPlaceholder
|
||||
var artistName = cursor.getString(artistIdIndex) ?: artistPlaceholder
|
||||
var artistName = cursor.getString(artistNameIndex) ?: artistPlaceholder
|
||||
val year = cursor.getInt(yearIndex)
|
||||
val coverUri = id.toAlbumArtURI()
|
||||
|
||||
|
@ -144,7 +148,7 @@ class MusicLoader(private val context: Context) {
|
|||
Media.TITLE, // 2
|
||||
Media.ALBUM_ID, // 3
|
||||
Media.TRACK, // 4
|
||||
Media.DURATION // 5
|
||||
Media.DURATION, // 5
|
||||
),
|
||||
selector, args,
|
||||
Media.DEFAULT_SORT_ORDER
|
||||
|
|
|
@ -75,20 +75,14 @@ class MusicStore private constructor() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Find a song from this instance in a safe manner.
|
||||
* Using a normal search of the songs list runs the risk of getting the *wrong* song with
|
||||
* the same name, so the album name is also used to fix the above problem.
|
||||
* FIXME: Artist names are more unique than album names, use those
|
||||
* @param name The name of the song
|
||||
* @param albumName The name of the song's album.
|
||||
* @return The song requested, null if there isnt one.
|
||||
* Find a song in a faster manner using a hash for its album as well.
|
||||
*/
|
||||
fun findSong(name: String, albumName: String): Song? {
|
||||
return albums.find { it.name == albumName }?.songs?.find { it.name == name }
|
||||
fun findSongFast(songHash: Int, albumHash: Int): Song? {
|
||||
return albums.find { it.hash == albumHash }?.songs?.find { it.hash == songHash }
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a song for a [uri], this is similar to [findSong], but with some kind of content uri.
|
||||
* Find a song for a [uri], this is similar to [findSongFast], but with some kind of content uri.
|
||||
* @return The corresponding [Song] for this [uri], null if there isnt one.
|
||||
*/
|
||||
fun findSongForUri(uri: Uri, resolver: ContentResolver): Song? {
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.oxycblt.auxio.settings.SettingsManager
|
|||
* All access should be done with [PlaybackStateManager.getInstance].
|
||||
*
|
||||
* TODO: Queues should reflect sort mode
|
||||
* TODO: Update loop mode to actually make sense [#13]
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackStateManager private constructor() {
|
||||
|
@ -605,10 +604,9 @@ class PlaybackStateManager private constructor() {
|
|||
*/
|
||||
private fun packToPlaybackState(): PlaybackState {
|
||||
return PlaybackState(
|
||||
songName = mSong?.name ?: "",
|
||||
songAlbumName = mSong?.album?.name ?: "",
|
||||
songHash = mSong?.hash ?: Int.MIN_VALUE,
|
||||
position = mPosition,
|
||||
parentName = mParent?.name ?: "",
|
||||
parentHash = mParent?.hash ?: Int.MIN_VALUE,
|
||||
index = mIndex,
|
||||
mode = mMode.toInt(),
|
||||
isShuffling = mIsShuffling,
|
||||
|
@ -625,11 +623,11 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
// Do queue setup first
|
||||
mMode = PlaybackMode.fromInt(playbackState.mode) ?: PlaybackMode.ALL_SONGS
|
||||
mParent = findParent(playbackState.parentName, mMode)
|
||||
mParent = findParent(playbackState.parentHash, mMode)
|
||||
mIndex = playbackState.index
|
||||
|
||||
// Then set up the current state
|
||||
mSong = musicStore.findSong(playbackState.songName, playbackState.songAlbumName)
|
||||
mSong = musicStore.songs.find { it.hash == playbackState.songHash }
|
||||
mLoopMode = LoopMode.fromInt(playbackState.loopMode) ?: LoopMode.NONE
|
||||
mIsShuffling = playbackState.isShuffling
|
||||
mIsInUserQueue = playbackState.inUserQueue
|
||||
|
@ -647,12 +645,12 @@ class PlaybackStateManager private constructor() {
|
|||
var queueItemId = 0L
|
||||
|
||||
mUserQueue.forEach { song ->
|
||||
unified.add(QueueItem(queueItemId, song.name, song.album.name, true))
|
||||
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, true))
|
||||
queueItemId++
|
||||
}
|
||||
|
||||
mQueue.forEach { song ->
|
||||
unified.add(QueueItem(queueItemId, song.name, song.album.name, false))
|
||||
unified.add(QueueItem(queueItemId, song.hash, song.album.hash, false))
|
||||
queueItemId++
|
||||
}
|
||||
|
||||
|
@ -664,8 +662,8 @@ class PlaybackStateManager private constructor() {
|
|||
* @param queueItems The list of [QueueItem]s to unpack.
|
||||
*/
|
||||
private fun unpackQueue(queueItems: List<QueueItem>) {
|
||||
queueItems.forEach { item ->
|
||||
musicStore.findSong(item.songName, item.albumName)?.let { song ->
|
||||
for (item in queueItems) {
|
||||
musicStore.findSongFast(item.songHash, item.albumHash)?.let { song ->
|
||||
if (item.isUserQueue) {
|
||||
mUserQueue.add(song)
|
||||
} else {
|
||||
|
@ -689,15 +687,14 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a [Parent] from music store given a [name] and playback [mode].
|
||||
* Get a [Parent] from music store given a [hash] and PlaybackMode [mode].
|
||||
*/
|
||||
private fun findParent(name: String, mode: PlaybackMode): Parent? {
|
||||
private fun findParent(hash: Int, mode: PlaybackMode): Parent? {
|
||||
return when (mode) {
|
||||
PlaybackMode.IN_GENRE -> musicStore.genres.find { it.name == name }
|
||||
PlaybackMode.IN_ARTIST -> musicStore.artists.find { it.name == name }
|
||||
PlaybackMode.IN_ALBUM -> musicStore.albums.find { it.name == name }
|
||||
|
||||
else -> null
|
||||
PlaybackMode.IN_GENRE -> musicStore.genres.find { it.hash == hash }
|
||||
PlaybackMode.IN_ARTIST -> musicStore.artists.find { it.hash == hash }
|
||||
PlaybackMode.IN_ALBUM -> musicStore.albums.find { it.hash == hash }
|
||||
PlaybackMode.ALL_SONGS -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.oxycblt.auxio.settings.blacklist.BlacklistDialog
|
|||
import org.oxycblt.auxio.settings.ui.IntListPrefDialog
|
||||
import org.oxycblt.auxio.settings.ui.IntListPreference
|
||||
import org.oxycblt.auxio.ui.Accent
|
||||
import org.oxycblt.auxio.ui.showToast
|
||||
|
||||
/**
|
||||
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
|
||||
|
@ -129,7 +130,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
SettingsManager.KEY_SAVE_STATE -> {
|
||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
playbackModel.savePlaybackState(requireContext()) {
|
||||
requireContext().getString(R.string.label_state_saved)
|
||||
requireContext().showToast(R.string.label_state_saved)
|
||||
}
|
||||
|
||||
true
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.Point
|
|||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Looper
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
|
@ -138,6 +139,24 @@ fun Context.showToast(@StringRes str: Int) {
|
|||
Toast.makeText(applicationContext, getString(str), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we are on a background thread.
|
||||
*/
|
||||
fun assertBackgroundThread() {
|
||||
check(Looper.myLooper() != Looper.getMainLooper()) {
|
||||
"This operation must be ran on a background thread."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that we are on a foreground thread.
|
||||
*/
|
||||
fun assertMainThread() {
|
||||
check(Looper.myLooper() == Looper.getMainLooper()) {
|
||||
"This operation must be ran on the main thread"
|
||||
}
|
||||
}
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.oxycblt.auxio.ui
|
||||
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -42,9 +41,7 @@ class MemberBinder<T : ViewDataBinding>(
|
|||
}
|
||||
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
|
||||
check(Looper.myLooper() == Looper.getMainLooper()) {
|
||||
"View can only be accessed on the main thread."
|
||||
}
|
||||
assertMainThread()
|
||||
|
||||
val binding = fragmentBinding
|
||||
|
||||
|
|
Loading…
Reference in a new issue