playback: fix queue moves
Fix moving items with the new queue system. This took a bit of thinking, but I think this is the correct way to implement this in a future-proof manner.
This commit is contained in:
parent
5adc87550e
commit
bef4dca0ce
8 changed files with 96 additions and 47 deletions
|
@ -23,10 +23,10 @@ import android.database.sqlite.SQLiteDatabase
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
import android.database.sqlite.SQLiteOpenHelper
|
||||||
import androidx.core.database.getIntOrNull
|
import androidx.core.database.getIntOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import org.oxycblt.auxio.music.tags.Date
|
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.parsing.correctWhitespace
|
import org.oxycblt.auxio.music.parsing.correctWhitespace
|
||||||
import org.oxycblt.auxio.music.parsing.splitEscaped
|
import org.oxycblt.auxio.music.parsing.splitEscaped
|
||||||
|
import org.oxycblt.auxio.music.tags.Date
|
||||||
import org.oxycblt.auxio.util.*
|
import org.oxycblt.auxio.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -27,7 +27,6 @@ import androidx.annotation.RequiresApi
|
||||||
import androidx.core.database.getIntOrNull
|
import androidx.core.database.getIntOrNull
|
||||||
import androidx.core.database.getStringOrNull
|
import androidx.core.database.getStringOrNull
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import org.oxycblt.auxio.music.tags.Date
|
|
||||||
import org.oxycblt.auxio.music.MusicSettings
|
import org.oxycblt.auxio.music.MusicSettings
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
||||||
|
@ -38,6 +37,7 @@ import org.oxycblt.auxio.music.storage.mediaStoreVolumeNameCompat
|
||||||
import org.oxycblt.auxio.music.storage.safeQuery
|
import org.oxycblt.auxio.music.storage.safeQuery
|
||||||
import org.oxycblt.auxio.music.storage.storageVolumesCompat
|
import org.oxycblt.auxio.music.storage.storageVolumesCompat
|
||||||
import org.oxycblt.auxio.music.storage.useQuery
|
import org.oxycblt.auxio.music.storage.useQuery
|
||||||
|
import org.oxycblt.auxio.music.tags.Date
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||||
|
|
|
@ -22,10 +22,10 @@ import androidx.core.text.isDigitsOnly
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.MetadataRetriever
|
import com.google.android.exoplayer2.MetadataRetriever
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import org.oxycblt.auxio.music.tags.Date
|
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
||||||
import org.oxycblt.auxio.music.storage.toAudioUri
|
import org.oxycblt.auxio.music.storage.toAudioUri
|
||||||
|
import org.oxycblt.auxio.music.tags.Date
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
|
||||||
|
|
|
@ -235,7 +235,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
||||||
val tokens =
|
val tokens =
|
||||||
// Match the input with the timestamp regex. If there is no match, see if we can
|
// Match the input with the timestamp regex. If there is no match, see if we can
|
||||||
// fall back to some kind of year value.
|
// fall back to some kind of year value.
|
||||||
(ISO8601_REGEX.matchEntire(timestamp) ?: return timestamp.toIntOrNull()?.let(Companion::from))
|
(ISO8601_REGEX.matchEntire(timestamp)
|
||||||
|
?: return timestamp.toIntOrNull()?.let(Companion::from))
|
||||||
.groupValues
|
.groupValues
|
||||||
// Filter to the specific tokens we want and convert them to integer tokens.
|
// Filter to the specific tokens we want and convert them to integer tokens.
|
||||||
.mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null }
|
.mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null }
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Auxio Project
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.tags
|
package org.oxycblt.auxio.music.tags
|
||||||
|
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
|
@ -5,8 +22,8 @@ import org.oxycblt.auxio.R
|
||||||
/**
|
/**
|
||||||
* The type of release an [Album] is considered. This includes EPs, Singles, Compilations, etc.
|
* The type of release an [Album] is considered. This includes EPs, Singles, Compilations, etc.
|
||||||
*
|
*
|
||||||
* This class is derived from the MusicBrainz Release Group Type specification. It can be found
|
* This class is derived from the MusicBrainz Release Group Type specification. It can be found at:
|
||||||
* at: https://musicbrainz.org/doc/Release_Group/Type
|
* https://musicbrainz.org/doc/Release_Group/Type
|
||||||
* @author Alexander Capehart (OxygenCobalt)
|
* @author Alexander Capehart (OxygenCobalt)
|
||||||
*/
|
*/
|
||||||
sealed class ReleaseType {
|
sealed class ReleaseType {
|
||||||
|
@ -21,8 +38,8 @@ sealed class ReleaseType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A plain album.
|
* A plain album.
|
||||||
* @param refinement A specification of what kind of performance this release is. If null,
|
* @param refinement A specification of what kind of performance this release is. If null, the
|
||||||
* the release is considered "Plain".
|
* release is considered "Plain".
|
||||||
*/
|
*/
|
||||||
data class Album(override val refinement: Refinement?) : ReleaseType() {
|
data class Album(override val refinement: Refinement?) : ReleaseType() {
|
||||||
override val stringRes: Int
|
override val stringRes: Int
|
||||||
|
@ -37,8 +54,8 @@ sealed class ReleaseType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs.
|
* A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs.
|
||||||
* @param refinement A specification of what kind of performance this release is. If null,
|
* @param refinement A specification of what kind of performance this release is. If null, the
|
||||||
* the release is considered "Plain".
|
* release is considered "Plain".
|
||||||
*/
|
*/
|
||||||
data class EP(override val refinement: Refinement?) : ReleaseType() {
|
data class EP(override val refinement: Refinement?) : ReleaseType() {
|
||||||
override val stringRes: Int
|
override val stringRes: Int
|
||||||
|
@ -53,8 +70,8 @@ sealed class ReleaseType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single. Usually a release consisting of 1-2 songs.
|
* A single. Usually a release consisting of 1-2 songs.
|
||||||
* @param refinement A specification of what kind of performance this release is. If null,
|
* @param refinement A specification of what kind of performance this release is. If null, the
|
||||||
* the release is considered "Plain".
|
* release is considered "Plain".
|
||||||
*/
|
*/
|
||||||
data class Single(override val refinement: Refinement?) : ReleaseType() {
|
data class Single(override val refinement: Refinement?) : ReleaseType() {
|
||||||
override val stringRes: Int
|
override val stringRes: Int
|
||||||
|
@ -69,8 +86,8 @@ sealed class ReleaseType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A compilation. Usually consists of many songs from a variety of artists.
|
* A compilation. Usually consists of many songs from a variety of artists.
|
||||||
* @param refinement A specification of what kind of performance this release is. If null,
|
* @param refinement A specification of what kind of performance this release is. If null, the
|
||||||
* the release is considered "Plain".
|
* release is considered "Plain".
|
||||||
*/
|
*/
|
||||||
data class Compilation(override val refinement: Refinement?) : ReleaseType() {
|
data class Compilation(override val refinement: Refinement?) : ReleaseType() {
|
||||||
override val stringRes: Int
|
override val stringRes: Int
|
||||||
|
@ -108,8 +125,8 @@ sealed class ReleaseType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Mix-tape. These are usually [EP]-sized releases of music made to promote an [Artist] or
|
* A Mix-tape. These are usually [EP]-sized releases of music made to promote an [Artist] or a
|
||||||
* a future release.
|
* future release.
|
||||||
*/
|
*/
|
||||||
object Mixtape : ReleaseType() {
|
object Mixtape : ReleaseType() {
|
||||||
override val refinement: Refinement?
|
override val refinement: Refinement?
|
||||||
|
@ -133,7 +150,8 @@ sealed class ReleaseType {
|
||||||
* Parse a [ReleaseType] from a string formatted with the MusicBrainz Release Group Type
|
* Parse a [ReleaseType] from a string formatted with the MusicBrainz Release Group Type
|
||||||
* specification.
|
* specification.
|
||||||
* @param types A list of values consisting of valid release type values.
|
* @param types A list of values consisting of valid release type values.
|
||||||
* @return A [ReleaseType] consisting of the given types, or null if the types were not valid.
|
* @return A [ReleaseType] consisting of the given types, or null if the types were not
|
||||||
|
* valid.
|
||||||
*/
|
*/
|
||||||
fun parse(types: List<String>): ReleaseType? {
|
fun parse(types: List<String>): ReleaseType? {
|
||||||
val primary = types.getOrNull(0) ?: return null
|
val primary = types.getOrNull(0) ?: return null
|
||||||
|
@ -150,12 +168,12 @@ sealed class ReleaseType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted
|
* Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted with
|
||||||
* with the MusicBrainz Release Group Type specification.
|
* the MusicBrainz Release Group Type specification.
|
||||||
* @param index The index of the release type to parse.
|
* @param index The index of the release type to parse.
|
||||||
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] corresponding
|
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType]
|
||||||
* to the callee's context. This is used in order to handle secondary times that are
|
* corresponding to the callee's context. This is used in order to handle secondary times
|
||||||
* actually [Refinement]s.
|
* that are actually [Refinement]s.
|
||||||
* @return A [ReleaseType] corresponding to the secondary type found at that index.
|
* @return A [ReleaseType] corresponding to the secondary type found at that index.
|
||||||
*/
|
*/
|
||||||
private inline fun List<String>.parseSecondaryTypes(
|
private inline fun List<String>.parseSecondaryTypes(
|
||||||
|
@ -174,12 +192,12 @@ sealed class ReleaseType {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to
|
* Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to any
|
||||||
* any child values.
|
* child values.
|
||||||
* @param type The release type value to parse.
|
* @param type The release type value to parse.
|
||||||
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] corresponding
|
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType]
|
||||||
* to the callee's context. This is used in order to handle secondary times that are
|
* corresponding to the callee's context. This is used in order to handle secondary times
|
||||||
* actually [Refinement]s.
|
* that are actually [Refinement]s.
|
||||||
*/
|
*/
|
||||||
private inline fun parseSecondaryTypeImpl(
|
private inline fun parseSecondaryTypeImpl(
|
||||||
type: String?,
|
type: String?,
|
||||||
|
|
|
@ -112,6 +112,21 @@ class Queue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reformat the queue's internal representation to align with the given values. This is not
|
||||||
|
* useful in most circumstances.
|
||||||
|
* @param
|
||||||
|
*/
|
||||||
|
fun rework(heap: List<Song?>, orderedMapping: IntArray, shuffledMapping: IntArray) {
|
||||||
|
// val instructions = mutableListOf<Int?>()
|
||||||
|
// val currentBackshift = 0
|
||||||
|
// for (song in heap) {
|
||||||
|
// if (song == null) {
|
||||||
|
// instructions.add(0, )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add [Song]s to the top of the queue. Will start playback if nothing is playing.
|
* Add [Song]s to the top of the queue. Will start playback if nothing is playing.
|
||||||
* @param songs The [Song]s to add.
|
* @param songs The [Song]s to add.
|
||||||
|
@ -179,18 +194,17 @@ class Queue {
|
||||||
orderedMapping.add(dst, orderedMapping.removeAt(src))
|
orderedMapping.add(dst, orderedMapping.removeAt(src))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I really need to figure out how to get non-swap moves working.
|
when (index) {
|
||||||
return when (index) {
|
// We are moving the currently playing song, correct the index to it's new position.
|
||||||
src -> {
|
src -> index = dst
|
||||||
index = dst
|
// We have moved an song from behind the playing song to in front, shift back.
|
||||||
ChangeResult.INDEX
|
in (src + 1)..dst -> index -= 1
|
||||||
}
|
// We have moved an song from in front of the playing song to behind, shift forward.
|
||||||
dst -> {
|
in dst until src -> index += 1
|
||||||
index = src
|
|
||||||
ChangeResult.INDEX
|
|
||||||
}
|
|
||||||
else -> ChangeResult.MAPPING
|
else -> ChangeResult.MAPPING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ChangeResult.INDEX
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Auxio Project
|
||||||
|
*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.library
|
package org.oxycblt.auxio.music.library
|
||||||
|
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
@ -5,10 +22,6 @@ import org.oxycblt.auxio.music.Song
|
||||||
class LibraryTest {
|
class LibraryTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LIBRARY = listOf(
|
val LIBRARY = listOf(Song.Raw())
|
||||||
Song.Raw(
|
|
||||||
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -30,7 +30,8 @@ class ReleaseTypeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun releaseType_parse_secondary() {
|
fun releaseType_parse_secondary() {
|
||||||
assertEquals(ReleaseType.Compilation(null), ReleaseType.parse(listOf("album", "compilation")))
|
assertEquals(
|
||||||
|
ReleaseType.Compilation(null), ReleaseType.parse(listOf("album", "compilation")))
|
||||||
assertEquals(ReleaseType.Soundtrack, ReleaseType.parse(listOf("album", "soundtrack")))
|
assertEquals(ReleaseType.Soundtrack, ReleaseType.parse(listOf("album", "soundtrack")))
|
||||||
assertEquals(ReleaseType.Mix, ReleaseType.parse(listOf("album", "dj-mix")))
|
assertEquals(ReleaseType.Mix, ReleaseType.parse(listOf("album", "dj-mix")))
|
||||||
assertEquals(ReleaseType.Mixtape, ReleaseType.parse(listOf("album", "mixtape/street")))
|
assertEquals(ReleaseType.Mixtape, ReleaseType.parse(listOf("album", "mixtape/street")))
|
||||||
|
@ -39,7 +40,8 @@ class ReleaseTypeTest {
|
||||||
@Test
|
@Test
|
||||||
fun releaseType_parse_modifiers() {
|
fun releaseType_parse_modifiers() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ReleaseType.Album(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("album", "live")))
|
ReleaseType.Album(ReleaseType.Refinement.LIVE),
|
||||||
|
ReleaseType.parse(listOf("album", "live")))
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ReleaseType.Album(ReleaseType.Refinement.REMIX),
|
ReleaseType.Album(ReleaseType.Refinement.REMIX),
|
||||||
ReleaseType.parse(listOf("album", "remix")))
|
ReleaseType.parse(listOf("album", "remix")))
|
||||||
|
@ -75,7 +77,8 @@ class ReleaseTypeTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun releaseType_parse_orphanedModifier() {
|
fun releaseType_parse_orphanedModifier() {
|
||||||
assertEquals(ReleaseType.Album(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("live")))
|
assertEquals(
|
||||||
|
ReleaseType.Album(ReleaseType.Refinement.LIVE), ReleaseType.parse(listOf("live")))
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ReleaseType.Album(ReleaseType.Refinement.REMIX), ReleaseType.parse(listOf("remix")))
|
ReleaseType.Album(ReleaseType.Refinement.REMIX), ReleaseType.parse(listOf("remix")))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue