music: keep changes when unshuffling/reshuffling

Keep changes when unshuffling and reshuffling the queue.

This quirk was a hold-over from the old queue system, and now it's
removed.

Note that sorting is still based on parent, and so sort orders might
remain somewhat wonky. I only see myself really tackling that come
gapless playback, as I have to remove that last vestige to get that
system working.
This commit is contained in:
Alexander Capehart 2022-09-16 20:12:00 -06:00
parent 9e9e1a007d
commit 765f2f9a18
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
21 changed files with 129 additions and 162 deletions

View file

@ -131,7 +131,7 @@ class AlbumDetailFragment :
check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" }
when (settings.detailPlaybackMode) { when (settings.detailPlaybackMode) {
null, MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) null, MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.SONGS -> playbackModel.play(item) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> if (item.genres.size > 1) { MusicMode.GENRES -> if (item.genres.size > 1) {
navModel.mainNavigateTo( navModel.mainNavigateTo(
@ -151,11 +151,11 @@ class AlbumDetailFragment :
} }
override fun onPlayParent() { override fun onPlayParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value), false) playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value))
} }
override fun onShuffleParent() { override fun onShuffleParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentAlbum.value), true) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentAlbum.value))
} }
override fun onShowSortMenu(anchor: View) { override fun onShowSortMenu(anchor: View) {

View file

@ -123,7 +123,7 @@ class ArtistDetailFragment :
is Song -> { is Song -> {
when (settings.detailPlaybackMode) { when (settings.detailPlaybackMode) {
null, MusicMode.ARTISTS -> playbackModel.playFromArtist(item) null, MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.SONGS -> playbackModel.play(item) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.GENRES -> if (item.genres.size > 1) { MusicMode.GENRES -> if (item.genres.size > 1) {
navModel.mainNavigateTo( navModel.mainNavigateTo(
@ -150,11 +150,11 @@ class ArtistDetailFragment :
} }
override fun onPlayParent() { override fun onPlayParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value), false) playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value))
} }
override fun onShuffleParent() { override fun onShuffleParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentArtist.value), true) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentArtist.value))
} }
override fun onShowSortMenu(anchor: View) { override fun onShowSortMenu(anchor: View) {

View file

@ -123,7 +123,7 @@ class GenreDetailFragment :
check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" } check(item is Song) { "Unexpected datatype: ${item::class.simpleName}" }
when (settings.detailPlaybackMode) { when (settings.detailPlaybackMode) {
null -> playbackModel.playFromGenre(item, unlikelyToBeNull(detailModel.currentGenre.value)) null -> playbackModel.playFromGenre(item, unlikelyToBeNull(detailModel.currentGenre.value))
MusicMode.SONGS -> playbackModel.play(item) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> if (item.genres.size > 1) { MusicMode.GENRES -> if (item.genres.size > 1) {
@ -144,11 +144,11 @@ class GenreDetailFragment :
} }
override fun onPlayParent() { override fun onPlayParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentGenre.value), false) playbackModel.play(unlikelyToBeNull(detailModel.currentGenre.value))
} }
override fun onShuffleParent() { override fun onShuffleParent() {
playbackModel.play(unlikelyToBeNull(detailModel.currentGenre.value), true) playbackModel.shuffle(unlikelyToBeNull(detailModel.currentGenre.value))
} }
override fun onShowSortMenu(anchor: View) { override fun onShowSortMenu(anchor: View) {

View file

@ -113,7 +113,7 @@ class SongListFragment : HomeListFragment<Song>() {
override fun onItemClick(item: Item) { override fun onItemClick(item: Item) {
check(item is Song) { "Unexpected datatype: ${item::class.java}" } check(item is Song) { "Unexpected datatype: ${item::class.java}" }
when (settings.libPlaybackMode) { when (settings.libPlaybackMode) {
MusicMode.SONGS -> playbackModel.play(item) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> if (item.genres.size > 1) { MusicMode.GENRES -> if (item.genres.size > 1) {

View file

@ -26,6 +26,7 @@ import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Date.Companion.from import org.oxycblt.auxio.music.Date.Companion.from
import org.oxycblt.auxio.music.extractor.parseId3GenreNames
import org.oxycblt.auxio.music.extractor.parseMultiValue import org.oxycblt.auxio.music.extractor.parseMultiValue
import org.oxycblt.auxio.music.extractor.parseReleaseType import org.oxycblt.auxio.music.extractor.parseReleaseType
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
@ -40,6 +41,8 @@ import java.util.UUID
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
// TODO: Make empty parents a hard error
// --- MUSIC MODELS --- // --- MUSIC MODELS ---
/** [Item] variant that represents a music item. */ /** [Item] variant that represents a music item. */
@ -204,11 +207,11 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
update(raw.albumName) update(raw.albumName)
update(raw.date) update(raw.date)
update(raw.artistNames)
update(raw.albumArtistNames)
update(raw.track) update(raw.track)
update(raw.disc) update(raw.disc)
update(raw.artistNames)
update(raw.albumArtistNames)
} }
override val rawName = requireNotNull(raw.name) { "Invalid raw: No title" } override val rawName = requireNotNull(raw.name) { "Invalid raw: No title" }
@ -317,7 +320,8 @@ class Song constructor(raw: Raw, settings: Settings) : Music() {
} }
) )
val _rawGenres = raw.genreNames.map { Genre.Raw(it) }.ifEmpty { listOf(Genre.Raw(null)) } val _rawGenres = raw.genreNames.parseId3GenreNames(settings)
.map { Genre.Raw(it) }.ifEmpty { listOf(Genre.Raw(null)) }
fun _link(album: Album) { fun _link(album: Album) {
_album = album _album = album
@ -379,7 +383,7 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
override fun resolveName(context: Context) = rawName override fun resolveName(context: Context) = rawName
/** The latest date this album was released. */ /** The earliest date this album was released. */
val date: Date? val date: Date?
/** The release type of this album, such as "EP". Defaults to "Album". */ /** The release type of this album, such as "EP". Defaults to "Album". */
@ -435,7 +439,6 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
} }
totalDuration += song.durationMs totalDuration += song.durationMs
} }
date = earliestDate date = earliestDate
@ -528,11 +531,11 @@ class Genre constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
val durationMs: Long val durationMs: Long
init { init {
val totalDuration = 0L var totalDuration = 0L
for (song in songs) { for (song in songs) {
song._link(this) song._link(this)
durationMs += song.durationMs totalDuration += song.durationMs
} }
durationMs = totalDuration durationMs = totalDuration

View file

@ -20,7 +20,7 @@ package org.oxycblt.auxio.music.extractor
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
/** TODO: Stub class, not implemented yet */ /** TODO: Stub class, not implemented yet */
class CacheLayer { class CacheDatabase {
fun init() { fun init() {
} }

View file

@ -68,9 +68,7 @@ import java.io.File
* to something that actually works, not even in Android 12. ID3v2.4 has been around for *21 * to something that actually works, not even in Android 12. ID3v2.4 has been around for *21
* years.* *It can drink now.* * years.* *It can drink now.*
* *
* Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums * Not to mention all the other infuriating quirks. Pretty much every OEM has added some extension
* table, so we have to go for the less efficient "make a big query on all the songs lol" method so
* that songs don't end up fragmented across artists. Pretty much every OEM has added some extension
* or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) crippling the * or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) crippling the
* normal tables so that you're railroaded into their music app. I have to use a semi-deprecated * normal tables so that you're railroaded into their music app. I have to use a semi-deprecated
* field to work with file paths, and the supposedly "modern" method is SLOWER and causes even more * field to work with file paths, and the supposedly "modern" method is SLOWER and causes even more
@ -82,12 +80,12 @@ import java.io.File
* Is there anything we can do about it? No. Google has routinely shut down issues that begged * Is there anything we can do about it? No. Google has routinely shut down issues that begged
* google to fix glaring issues with MediaStore or to just take the API behind the woodshed and * google to fix glaring issues with MediaStore or to just take the API behind the woodshed and
* shoot it. Largely because they have zero incentive to improve it given how "obscure" local music * shoot it. Largely because they have zero incentive to improve it given how "obscure" local music
* listening is. As a result, Auxio exposes an option to use an internal parser based on ExoPlayer * listening is. As a result, I am forced to write my own extractor (Which is the contents of the
* that at least tries to correct the insane metadata that this API returns, but not only is that * rest of this module) based on ExoPlayer that at least tries to correct the insane metadata that
* system horrifically slow and bug-prone, it also faces the even larger issue of how google keeps * this API returns, but not only is that system horrifically slow and bug-prone, it also faces the
* trying to kill the filesystem and force you into their ContentResolver API. In the future * even larger issue of how google keeps trying to kill the filesystem and force you into their
* MediaStore could be the only system we have, which is also the day that greenland melts and * ContentResolver API. In the future MediaStore could be the only system we have, which is also
* birthdays stop happening forever. * the day that greenland melts and birthdays stop happening forever.
* *
* I'm pretty sure nothing is going to happen and MediaStore will continue to be neglected and * I'm pretty sure nothing is going to happen and MediaStore will continue to be neglected and
* probably deprecated eventually for a "new" API that just coincidentally excludes music indexing. * probably deprecated eventually for a "new" API that just coincidentally excludes music indexing.
@ -102,7 +100,7 @@ import java.io.File
* music loading process. * music loading process.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheLayer) { abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheDatabase) {
private var cursor: Cursor? = null private var cursor: Cursor? = null
private var idIndex = -1 private var idIndex = -1
@ -249,7 +247,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
* This returns true if the song could be restored from cache, false if metadata had to be * This returns true if the song could be restored from cache, false if metadata had to be
* re-extracted, and null if the cursor is exhausted. * re-extracted, and null if the cursor is exhausted.
*/ */
fun populateRaw(raw: Song.Raw): Boolean? { fun populateRawSong(raw: Song.Raw): Boolean? {
val cursor = requireNotNull(cursor) { "MediaStoreLayer is not properly initialized" } val cursor = requireNotNull(cursor) { "MediaStoreLayer is not properly initialized" }
if (!cursor.moveToNext()) { if (!cursor.moveToNext()) {
logD("Cursor is exhausted") logD("Cursor is exhausted")
@ -374,7 +372,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
* API 21 onwards to API 29. * API 21 onwards to API 29.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : class Api21MediaStoreLayer(context: Context, cacheLayer: CacheDatabase) :
MediaStoreLayer(context, cacheLayer) { MediaStoreLayer(context, cacheLayer) {
private var trackIndex = -1 private var trackIndex = -1
private var dataIndex = -1 private var dataIndex = -1
@ -440,7 +438,7 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheDatabase) :
MediaStoreLayer(context, cacheLayer) { MediaStoreLayer(context, cacheLayer) {
private var volumeIndex = -1 private var volumeIndex = -1
private var relativePathIndex = -1 private var relativePathIndex = -1
@ -496,7 +494,7 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheDatabase) :
BaseApi29MediaStoreLayer(context, cacheLayer) { BaseApi29MediaStoreLayer(context, cacheLayer) {
private var trackIndex = -1 private var trackIndex = -1
@ -528,7 +526,7 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : class Api30MediaStoreLayer(context: Context, cacheLayer: CacheDatabase) :
BaseApi29MediaStoreLayer(context, cacheLayer) { BaseApi29MediaStoreLayer(context, cacheLayer) {
private var trackIndex: Int = -1 private var trackIndex: Int = -1
private var discIndex: Int = -1 private var discIndex: Int = -1

View file

@ -55,7 +55,7 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M
suspend fun parse(emit: suspend (Song.Raw) -> Unit) { suspend fun parse(emit: suspend (Song.Raw) -> Unit) {
while (true) { while (true) {
val raw = Song.Raw() val raw = Song.Raw()
if (mediaStoreLayer.populateRaw(raw) ?: break) { if (mediaStoreLayer.populateRawSong(raw) ?: break) {
// No need to extract metadata that was successfully restored from the cache // No need to extract metadata that was successfully restored from the cache
emit(raw) emit(raw)
continue continue

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.dirs package org.oxycblt.auxio.music.settings
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.dirs package org.oxycblt.auxio.music.settings
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.dirs package org.oxycblt.auxio.music.settings
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.separators package org.oxycblt.auxio.music.settings
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater

View file

@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer
import org.oxycblt.auxio.music.extractor.CacheLayer import org.oxycblt.auxio.music.extractor.CacheDatabase
import org.oxycblt.auxio.music.extractor.MetadataLayer import org.oxycblt.auxio.music.extractor.MetadataLayer
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -202,7 +202,7 @@ class Indexer {
// experience. This is technically dependency injection. Except it doesn't increase // experience. This is technically dependency injection. Except it doesn't increase
// your compile times by 3x. Isn't that nice. // your compile times by 3x. Isn't that nice.
val cacheLayer = CacheLayer() val cacheLayer = CacheDatabase()
val mediaStoreLayer = val mediaStoreLayer =
when { when {

View file

@ -38,7 +38,6 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logE
/** /**
* The ViewModel that provides a UI frontend for [PlaybackStateManager]. * The ViewModel that provides a UI frontend for [PlaybackStateManager].
@ -92,72 +91,70 @@ class PlaybackViewModel(application: Application) :
// --- PLAYING FUNCTIONS --- // --- PLAYING FUNCTIONS ---
/** Play a [song] from all songs. */ /** Play a [song] from all songs. */
fun play(song: Song) { fun playFromAll(song: Song) {
playbackManager.play(song, null, settings) playbackManager.play(song, null, settings)
} }
/** Play a song from it's album. */
fun playFromAlbum(song: Song) {
playbackManager.play(song, song.album, settings)
}
/** Play a song from it's artist. */
fun playFromArtist(song: Song) {
playbackManager.play(song, song.album.artist, settings)
}
/** Play a song from the specific genre that contains the song. */
fun playFromGenre(song: Song, genre: Genre) {
if (!genre.songs.contains(song)) {
logE("Genre does not contain song, not playing")
return
}
playbackManager.play(song, genre, settings)
}
/**
* Play an [album].
* @param shuffled Whether to shuffle the new queue
*/
fun play(album: Album, shuffled: Boolean) {
if (album.songs.isEmpty()) {
logE("Album is empty, Not playing")
return
}
playbackManager.play(album, shuffled, settings)
}
/**
* Play an [artist].
* @param shuffled Whether to shuffle the new queue
*/
fun play(artist: Artist, shuffled: Boolean) {
if (artist.songs.isEmpty()) {
logE("Artist is empty, Not playing")
return
}
playbackManager.play(artist, shuffled, settings)
}
/**
* Play a [genre].
* @param shuffled Whether to shuffle the new queue
*/
fun play(genre: Genre, shuffled: Boolean) {
if (genre.songs.isEmpty()) {
logE("Genre is empty, Not playing")
return
}
playbackManager.play(genre, shuffled, settings)
}
/** Shuffle all songs */ /** Shuffle all songs */
fun shuffleAll() { fun shuffleAll() {
playbackManager.shuffleAll(settings) playbackManager.play(null, null, settings, true)
}
/** Play a song from it's album. */
fun playFromAlbum(song: Song) {
playbackManager.play(song, song.album, settings, false)
}
/** Play a song from it's artist. */
fun playFromArtist(song: Song) {
playbackManager.play(song, song.album.artist, settings, false)
}
/** Play a song from the specific genre that contains the song. */
fun playFromGenre(song: Song, genre: Genre) {
playbackManager.play(song, genre, settings, false)
}
/**
* Play an [album].
*/
fun play(album: Album) {
playbackManager.play(null, album, settings, false)
}
/**
* Play an [artist].
*/
fun play(artist: Artist) {
playbackManager.play(null, artist, settings, false)
}
/**
* Play a [genre].
*/
fun play(genre: Genre) {
playbackManager.play(null, genre, settings, false)
}
/**
* Shuffle an [album].
*/
fun shuffle(album: Album) {
playbackManager.play(null, album, settings, true)
}
/**
* Shuffle an [artist].
*/
fun shuffle(artist: Artist) {
playbackManager.play(null, artist, settings, true)
}
/**
* Shuffle a [genre].
*/
fun shuffle(genre: Genre) {
playbackManager.play(null, genre, settings, true)
} }
/** /**

View file

@ -151,54 +151,27 @@ class PlaybackStateManager private constructor() {
/** Play a song from a parent that contains the song. */ /** Play a song from a parent that contains the song. */
@Synchronized @Synchronized
fun play(song: Song, parent: MusicParent?, settings: Settings) { fun play(
song: Song?,
parent: MusicParent?,
settings: Settings,
shuffled: Boolean = settings.keepShuffle && isShuffled
) {
val internalPlayer = internalPlayer ?: return val internalPlayer = internalPlayer ?: return
val library = musicStore.library ?: return val library = musicStore.library ?: return
this.parent = parent this.parent = parent
_queue = (parent?.songs ?: library.songs).toMutableList()
applyNewQueue(library, settings, settings.keepShuffle && isShuffled, song) orderQueue(settings, shuffled, song)
notifyNewPlayback() notifyNewPlayback()
notifyShuffledChanged() notifyShuffledChanged()
internalPlayer.loadSong(song, true) internalPlayer.loadSong(this.song, true)
isInitialized = true isInitialized = true
} }
/** Play a [parent], such as an artist or album. */
@Synchronized
fun play(parent: MusicParent, shuffled: Boolean, settings: Settings) {
val internalPlayer = internalPlayer ?: return
val library = musicStore.library ?: return
this.parent = parent
applyNewQueue(library, settings, shuffled, null)
notifyNewPlayback()
notifyShuffledChanged()
internalPlayer.loadSong(song, true)
isInitialized = true
}
/** Shuffle all songs. */
@Synchronized
fun shuffleAll(settings: Settings) {
val internalPlayer = internalPlayer ?: return
val library = musicStore.library ?: return
parent = null
applyNewQueue(library, settings, true, null)
notifyNewPlayback()
notifyShuffledChanged()
internalPlayer.loadSong(song, true)
isInitialized = true
}
// --- QUEUE FUNCTIONS --- // --- QUEUE FUNCTIONS ---
/** Go to the next song, along with doing all the checks that entails. */ /** Go to the next song, along with doing all the checks that entails. */
@ -288,27 +261,24 @@ class PlaybackStateManager private constructor() {
/** Set whether this instance is [shuffled]. Updates the queue accordingly. */ /** Set whether this instance is [shuffled]. Updates the queue accordingly. */
@Synchronized @Synchronized
fun reshuffle(shuffled: Boolean, settings: Settings) { fun reshuffle(shuffled: Boolean, settings: Settings) {
val library = musicStore.library ?: return
val song = song ?: return val song = song ?: return
applyNewQueue(library, settings, shuffled, song) orderQueue(settings, shuffled, song)
notifyQueueReworked() notifyQueueReworked()
notifyShuffledChanged() notifyShuffledChanged()
} }
private fun applyNewQueue( private fun orderQueue(
library: MusicStore.Library,
settings: Settings, settings: Settings,
shuffled: Boolean, shuffled: Boolean,
keep: Song? keep: Song?
) { ) {
val newQueue = (parent?.songs ?: library.songs).toMutableList()
val newIndex: Int val newIndex: Int
if (shuffled) { if (shuffled) {
newQueue.shuffle() _queue.shuffle()
if (keep != null) { if (keep != null) {
newQueue.add(0, newQueue.removeAt(newQueue.indexOf(keep))) _queue.add(0, _queue.removeAt(_queue.indexOf(keep)))
} }
newIndex = 0 newIndex = 0
@ -323,12 +293,11 @@ class PlaybackStateManager private constructor() {
} }
} }
sort.songsInPlace(newQueue) sort.songsInPlace(_queue)
newIndex = keep?.let(_queue::indexOf) ?: 0
newIndex = keep?.let(newQueue::indexOf) ?: 0
} }
_queue = newQueue _queue = queue
index = newIndex index = newIndex
isShuffled = shuffled isShuffled = shuffled
} }

View file

@ -163,7 +163,7 @@ class MediaSessionComponent(private val context: Context, private val callback:
builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, it.toLong()) builder.putLong(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, it.toLong())
} }
song.album.date?.let { song.date?.let {
builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, it.toString()) builder.putString(MediaMetadataCompat.METADATA_KEY_DATE, it.toString())
} }

View file

@ -382,7 +382,7 @@ class PlaybackService :
} }
} }
is InternalPlayer.Action.ShuffleAll -> { is InternalPlayer.Action.ShuffleAll -> {
playbackManager.shuffleAll(settings) playbackManager.play(null, null, settings, true)
} }
is InternalPlayer.Action.Open -> { is InternalPlayer.Action.Open -> {
library.findSongForUri(application, action.uri)?.let { song -> library.findSongForUri(application, action.uri)?.let { song ->

View file

@ -151,7 +151,7 @@ class SearchFragment :
override fun onItemClick(item: Item) { override fun onItemClick(item: Item) {
when (item) { when (item) {
is Song -> when (settings.libPlaybackMode) { is Song -> when (settings.libPlaybackMode) {
MusicMode.SONGS -> playbackModel.play(item) MusicMode.SONGS -> playbackModel.playFromAll(item)
MusicMode.ALBUMS -> playbackModel.playFromAlbum(item) MusicMode.ALBUMS -> playbackModel.playFromAlbum(item)
MusicMode.ARTISTS -> playbackModel.playFromArtist(item) MusicMode.ARTISTS -> playbackModel.playFromArtist(item)
MusicMode.GENRES -> if (item.genres.size > 1) { MusicMode.GENRES -> if (item.genres.size > 1) {

View file

@ -30,7 +30,7 @@ import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.MusicMode import org.oxycblt.auxio.music.MusicMode
import org.oxycblt.auxio.music.Sort import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.music.dirs.MusicDirs import org.oxycblt.auxio.music.settings.MusicDirs
import org.oxycblt.auxio.playback.BarAction import org.oxycblt.auxio.playback.BarAction
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp

View file

@ -96,10 +96,10 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
musicMenuImpl(anchor, menuRes) { id -> musicMenuImpl(anchor, menuRes) { id ->
when (id) { when (id) {
R.id.action_play -> { R.id.action_play -> {
playbackModel.play(album, false) playbackModel.play(album)
} }
R.id.action_shuffle -> { R.id.action_shuffle -> {
playbackModel.play(album, true) playbackModel.shuffle(album)
} }
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(album) playbackModel.playNext(album)
@ -131,10 +131,10 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
musicMenuImpl(anchor, menuRes) { id -> musicMenuImpl(anchor, menuRes) { id ->
when (id) { when (id) {
R.id.action_play -> { R.id.action_play -> {
playbackModel.play(artist, false) playbackModel.play(artist)
} }
R.id.action_shuffle -> { R.id.action_shuffle -> {
playbackModel.play(artist, true) playbackModel.shuffle(artist)
} }
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(artist) playbackModel.playNext(artist)
@ -163,10 +163,10 @@ abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
musicMenuImpl(anchor, menuRes) { id -> musicMenuImpl(anchor, menuRes) { id ->
when (id) { when (id) {
R.id.action_play -> { R.id.action_play -> {
playbackModel.play(genre, false) playbackModel.play(genre)
} }
R.id.action_shuffle -> { R.id.action_shuffle -> {
playbackModel.play(genre, true) playbackModel.shuffle(genre)
} }
R.id.action_play_next -> { R.id.action_play_next -> {
playbackModel.playNext(genre) playbackModel.playNext(genre)

View file

@ -83,12 +83,12 @@
tools:layout="@layout/dialog_pre_amp" /> tools:layout="@layout/dialog_pre_amp" />
<dialog <dialog
android:id="@+id/music_dirs_dialog" android:id="@+id/music_dirs_dialog"
android:name="org.oxycblt.auxio.music.dirs.MusicDirsDialog" android:name="org.oxycblt.auxio.music.settings.MusicDirsDialog"
android:label="music_dirs_dialog" android:label="music_dirs_dialog"
tools:layout="@layout/dialog_music_dirs" /> tools:layout="@layout/dialog_music_dirs" />
<dialog <dialog
android:id="@+id/separators_dialog" android:id="@+id/separators_dialog"
android:name="org.oxycblt.auxio.music.separators.SeparatorsDialog" android:name="org.oxycblt.auxio.music.settings.SeparatorsDialog"
android:label="music_dirs_dialog" android:label="music_dirs_dialog"
tools:layout="@layout/dialog_separators" /> tools:layout="@layout/dialog_separators" />