Skip to content

Commit f4edc45

Browse files
committed
better radio queue & shuffle support
1 parent c72677a commit f4edc45

File tree

13 files changed

+742
-400
lines changed

13 files changed

+742
-400
lines changed

app/src/main/java/io/github/zyrouge/symphony/services/database/store/SongQueueSongMappingStore.kt

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ abstract class SongQueueSongMappingStore {
5454
}
5555

5656
@RawQuery
57-
protected abstract fun findByNextIdRaw(query: SupportSQLiteQuery): Song.AlongSongQueueMapping?
57+
protected abstract fun findByNextId(query: SupportSQLiteQuery): Song.AlongSongQueueMapping?
5858

5959
fun findByNextId(queueId: String, nextId: String?): Song.AlongSongQueueMapping? {
6060
val query = "SELECT ${Song.TABLE}.*, " +
@@ -63,11 +63,24 @@ abstract class SongQueueSongMappingStore {
6363
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? AND ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_NEXT_ID} = ? " +
6464
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} "
6565
val args = arrayOf(queueId, nextId)
66-
return findByNextIdRaw(SimpleSQLiteQuery(query, args))
66+
return findByNextId(SimpleSQLiteQuery(query, args))
6767
}
6868

6969
@RawQuery
70-
protected abstract fun findHeadRaw(query: SupportSQLiteQuery): Song.AlongSongQueueMapping?
70+
protected abstract fun findBySongId(query: SupportSQLiteQuery): Song.AlongSongQueueMapping?
71+
72+
fun findBySongId(queueId: String, songId: String?): Song.AlongSongQueueMapping? {
73+
val query = "SELECT ${Song.TABLE}.*, " +
74+
"${SongQueueSongMapping.TABLE}.* " +
75+
"FROM ${SongQueueSongMapping.TABLE} " +
76+
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? AND ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} = ? " +
77+
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} "
78+
val args = arrayOf(queueId, songId)
79+
return findBySongId(SimpleSQLiteQuery(query, args))
80+
}
81+
82+
@RawQuery
83+
protected abstract fun findHead(query: SupportSQLiteQuery): Song.AlongSongQueueMapping?
7184

7285
fun findHead(queueId: String): Song.AlongSongQueueMapping? {
7386
val query = "SELECT ${Song.TABLE}.*, " +
@@ -76,10 +89,25 @@ abstract class SongQueueSongMappingStore {
7689
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? AND ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} = true " +
7790
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} "
7891
val args = arrayOf(queueId)
79-
return findHeadRaw(SimpleSQLiteQuery(query, args))
92+
return findHead(SimpleSQLiteQuery(query, args))
93+
}
94+
95+
protected abstract fun entries(query: SupportSQLiteQuery): Map<
96+
@MapColumn(SongQueueSongMapping.COLUMN_ID) String, Song.AlongSongQueueMapping>
97+
98+
fun entries(queueId: String): Map<
99+
String, Song.AlongSongQueueMapping> {
100+
val query = "SELECT ${Song.TABLE}.*, " +
101+
"${SongQueueSongMapping.TABLE}.* " +
102+
"FROM ${SongQueueSongMapping.TABLE} " +
103+
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? " +
104+
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} " +
105+
"ORDER BY ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} DESC"
106+
val args = arrayOf(queueId)
107+
return entries(SimpleSQLiteQuery(query, args))
80108
}
81109

82-
protected abstract fun entriesByIdsRaw(query: SupportSQLiteQuery): Map<
110+
protected abstract fun entriesByIds(query: SupportSQLiteQuery): Map<
83111
@MapColumn(SongQueueSongMapping.COLUMN_ID) String, Song.AlongSongQueueMapping>
84112

85113
fun entriesByIds(queueId: String, songMappingIds: List<String>): Map<
@@ -93,10 +121,10 @@ abstract class SongQueueSongMappingStore {
93121
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} " +
94122
"ORDER BY ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} DESC"
95123
val args = arrayOf(queueId, *songMappingIds.toTypedArray())
96-
return entriesByIdsRaw(SimpleSQLiteQuery(query, args))
124+
return entriesByIds(SimpleSQLiteQuery(query, args))
97125
}
98126

99-
protected abstract fun entriesByNextIdsRaw(query: SupportSQLiteQuery): Map<
127+
protected abstract fun entriesByNextIds(query: SupportSQLiteQuery): Map<
100128
@MapColumn(SongQueueSongMapping.COLUMN_NEXT_ID) String, Song.AlongSongQueueMapping>
101129

102130
fun entriesByNextIds(queueId: String, songMappingIds: List<String>): Map<
@@ -110,7 +138,24 @@ abstract class SongQueueSongMappingStore {
110138
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} " +
111139
"ORDER BY ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} DESC"
112140
val args = arrayOf(queueId, *songMappingIds.toTypedArray())
113-
return entriesByNextIdsRaw(SimpleSQLiteQuery(query, args))
141+
return entriesByNextIds(SimpleSQLiteQuery(query, args))
142+
}
143+
144+
protected abstract fun entriesBySongIds(query: SupportSQLiteQuery): Map<
145+
@MapColumn(SongQueueSongMapping.COLUMN_NEXT_ID) String, Song.AlongSongQueueMapping>
146+
147+
fun entriesBySongIds(queueId: String, songIds: List<String>): Map<
148+
String, Song.AlongSongQueueMapping> {
149+
val query = "SELECT ${Song.TABLE}.*, " +
150+
"${SongQueueSongMapping.TABLE}.* " +
151+
"FROM ${SongQueueSongMapping.TABLE} " +
152+
"WHERE ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ? " +
153+
"AND ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} " +
154+
"IN (${sqlqph(songIds.size)}) " +
155+
"LEFT JOIN ${Song.TABLE} ON ${Song.TABLE}.${Song.COLUMN_ID} = ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID} " +
156+
"ORDER BY ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_IS_HEAD} DESC"
157+
val args = arrayOf(queueId, *songIds.toTypedArray())
158+
return entriesBySongIds(SimpleSQLiteQuery(query, args))
114159
}
115160

116161
@RawQuery(observedEntities = [Song::class, SongQueueSongMapping::class])
@@ -129,24 +174,32 @@ abstract class SongQueueSongMappingStore {
129174
}
130175

131176
@OptIn(ExperimentalCoroutinesApi::class)
132-
fun transformEntriesAsValuesFlow(entries: Flow<Map<String, Song.AlongSongQueueMapping>>): Flow<List<Song>> {
133-
return entries.mapLatest {
134-
val list = mutableListOf<Song>()
135-
var head = it.firstNotNullOfOrNull {
136-
when {
137-
it.value.mapping.isHead -> it.value
138-
else -> null
139-
}
140-
}
141-
while (head != null) {
142-
list.add(head.entity)
143-
head = it[head.mapping.nextId]
177+
fun transformEntriesAsValues(entries: Map<String, Song.AlongSongQueueMapping>): List<Song.AlongSongQueueMapping> {
178+
val list = mutableListOf<Song.AlongSongQueueMapping>()
179+
var head = entries.firstNotNullOfOrNull {
180+
when {
181+
it.value.mapping.isHead -> it.value
182+
else -> null
144183
}
145-
list.toList()
146184
}
185+
while (head != null) {
186+
list.add(head)
187+
head = entries[head.mapping.nextId]
188+
}
189+
return list.toList()
190+
}
191+
192+
@OptIn(ExperimentalCoroutinesApi::class)
193+
fun transformEntriesAsValuesFlow(entriesFlow: Flow<Map<String, Song.AlongSongQueueMapping>>): Flow<List<Song.AlongSongQueueMapping>> {
194+
return entriesFlow.mapLatest { transformEntriesAsValues(it) }
195+
}
196+
197+
fun values(queueId: String): List<Song.AlongSongQueueMapping> {
198+
val entries = entries(queueId)
199+
return transformEntriesAsValues(entries)
147200
}
148201

149-
fun valuesAsFlow(queueId: String): Flow<List<Song>> {
202+
fun valuesAsFlow(queueId: String): Flow<List<Song.AlongSongQueueMapping>> {
150203
val entries = entriesAsFlow(queueId)
151204
return transformEntriesAsValuesFlow(entries)
152205
}

app/src/main/java/io/github/zyrouge/symphony/services/database/store/SongQueueStore.kt

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,35 @@ abstract class SongQueueStore {
2626
return delete(SimpleSQLiteQuery(query, ids))
2727
}
2828

29+
@RawQuery
30+
protected abstract fun findById(query: SimpleSQLiteQuery): SongQueue.AlongAttributes?
31+
32+
fun findById(id: String): SongQueue.AlongAttributes? {
33+
val query = "SELECT * FROM ${SongQueue.TABLE} WHERE ${SongQueue.COLUMN_ID} = ?"
34+
val args = arrayOf(id)
35+
return findById(SimpleSQLiteQuery(query, args))
36+
}
37+
2938
@RawQuery
3039
protected abstract fun findByInternalId(query: SimpleSQLiteQuery): SongQueue.AlongAttributes?
3140

3241
fun findByInternalId(internalId: Int): SongQueue.AlongAttributes? {
33-
val query = "SELECT * FROM ${SongQueue.TABLE} " +
34-
"WHERE ${SongQueue.COLUMN_INTERNAL_ID} = ?"
42+
val query = "SELECT ${SongQueue.TABLE}.*, " +
43+
"COUNT(${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID}) as ${SongQueue.AlongAttributes.EMBEDDED_TRACKS_COUNT} " +
44+
"FROM ${SongQueue.TABLE} " +
45+
"LEFT JOIN ${SongQueueSongMapping.TABLE} ON ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ${SongQueue.TABLE}.${SongQueue.COLUMN_ID}"
3546
val args = arrayOf(internalId)
3647
return findByInternalId(SimpleSQLiteQuery(query, args))
3748
}
3849

3950
@RawQuery(observedEntities = [SongQueue::class, SongQueueSongMapping::class])
40-
protected abstract fun findFirstAsFlow(query: SupportSQLiteQuery): Flow<SongQueue.AlongAttributes?>
51+
protected abstract fun findByInternalIdAsFlow(query: SupportSQLiteQuery): Flow<SongQueue.AlongAttributes?>
4152

42-
fun findFirstAsFlow(): Flow<SongQueue.AlongAttributes?> {
53+
fun findByInternalIdAsFlow(): Flow<SongQueue.AlongAttributes?> {
4354
val query = "SELECT ${SongQueue.TABLE}.*, " +
4455
"COUNT(${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID}) as ${SongQueue.AlongAttributes.EMBEDDED_TRACKS_COUNT} " +
4556
"FROM ${SongQueue.TABLE} " +
46-
"LEFT JOIN ${SongQueueSongMapping.TABLE} ON ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ${SongQueue.TABLE}.${SongQueue.COLUMN_ID} " +
47-
"LIMIT 1"
48-
return findFirstAsFlow(SimpleSQLiteQuery(query))
57+
"LEFT JOIN ${SongQueueSongMapping.TABLE} ON ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ${SongQueue.TABLE}.${SongQueue.COLUMN_ID}"
58+
return findByInternalIdAsFlow(SimpleSQLiteQuery(query))
4959
}
50-
51-
// @RawQuery(observedEntities = [SongQueue::class, SongQueueSongMapping::class])
52-
// fun valuesAsFlowRaw(query: SupportSQLiteQuery): Flow<List<SongQueue.AlongAttributes>>
53-
54-
//fun SongQueueStore.valuesAsFlow(): Flow<List<SongQueue.AlongAttributes>> {
55-
// val query = "SELECT ${SongQueue.TABLE}.*, " +
56-
// "COUNT(${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_SONG_ID}) as ${SongQueue.AlongAttributes.EMBEDDED_TRACKS_COUNT} " +
57-
// "FROM ${SongQueue.TABLE} " +
58-
// "LEFT JOIN ${SongQueueSongMapping.TABLE} ON ${SongQueueSongMapping.TABLE}.${SongQueueSongMapping.COLUMN_QUEUE_ID} = ${SongQueue.TABLE}.${SongQueue.COLUMN_ID}"
59-
// return valuesAsFlowRaw(SimpleSQLiteQuery(query))
60-
//}
6160
}

app/src/main/java/io/github/zyrouge/symphony/services/groove/entities/SongQueue.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import androidx.room.PrimaryKey
1212
@Entity(
1313
SongQueue.TABLE,
1414
foreignKeys = [
15+
ForeignKey(
16+
entity = SongQueue::class,
17+
parentColumns = arrayOf(SongQueue.COLUMN_ID),
18+
childColumns = arrayOf(SongQueue.COLUMN_ORIGINAL_ID),
19+
onDelete = ForeignKey.SET_NULL,
20+
),
1521
ForeignKey(
1622
entity = SongQueueSongMapping::class,
1723
parentColumns = arrayOf(SongQueueSongMapping.COLUMN_ID),
@@ -25,12 +31,16 @@ data class SongQueue(
2531
@PrimaryKey
2632
@ColumnInfo(COLUMN_ID)
2733
val id: String,
34+
@ColumnInfo(COLUMN_ORIGINAL_ID)
35+
val originalId: String? = null,
2836
@ColumnInfo(COLUMN_INTERNAL_ID)
2937
val internalId: Int? = null,
3038
@ColumnInfo(COLUMN_PLAYING_ID)
3139
val playingId: String?,
40+
@ColumnInfo(COLUMN_PLAYING_IS_PLAYING)
41+
val isPlaying: Boolean,
3242
@ColumnInfo(COLUMN_PLAYING_TIMESTAMP)
33-
val playingTimestamp: Long?,
43+
val playingTimestamp: Long,
3444
@ColumnInfo(COLUMN_PLAYING_SPEED_INT)
3545
val playingSpeedInt: Int,
3646
@ColumnInfo(COLUMN_PLAYING_PITCH_INT)
@@ -45,6 +55,8 @@ data class SongQueue(
4555
val pitchInt: Int,
4656
@ColumnInfo(COLUMN_PAUSE_ON_SONG_END)
4757
val pauseOnSongEnd: Boolean,
58+
@ColumnInfo(COLUMN_SLEEP_TIMER_ENDS_AT)
59+
val sleepTimerEndsAt: Long?,
4860
) {
4961
enum class LoopMode {
5062
None,
@@ -73,8 +85,10 @@ data class SongQueue(
7385
companion object {
7486
const val TABLE = "song_queue"
7587
const val COLUMN_ID = "id"
88+
const val COLUMN_ORIGINAL_ID = "original_id"
7689
const val COLUMN_INTERNAL_ID = "internal_id"
7790
const val COLUMN_PLAYING_ID = "playing_id"
91+
const val COLUMN_PLAYING_IS_PLAYING = "is_playing"
7892
const val COLUMN_PLAYING_SPEED_INT = "playing_speed_int"
7993
const val COLUMN_PLAYING_PITCH_INT = "playing_pitch_int"
8094
const val COLUMN_PLAYING_TIMESTAMP = "playing_timestamp"
@@ -83,6 +97,7 @@ data class SongQueue(
8397
const val COLUMN_SPEED_INT = "speed_int"
8498
const val COLUMN_PITCH_INT = "pitch_int"
8599
const val COLUMN_PAUSE_ON_SONG_END = "pause_on_song_end"
100+
const val COLUMN_SLEEP_TIMER_ENDS_AT = "sleep_timer_ends_at"
86101

87102
const val SPEED_MULTIPLIER = 100
88103
const val PITCH_MULTIPLIER = 100

app/src/main/java/io/github/zyrouge/symphony/services/groove/entities/SongQueueSongMapping.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,11 @@ import androidx.room.PrimaryKey
2929
childColumns = arrayOf(SongQueueSongMapping.COLUMN_NEXT_ID),
3030
onDelete = ForeignKey.SET_NULL,
3131
),
32-
ForeignKey(
33-
entity = SongQueueSongMapping::class,
34-
parentColumns = arrayOf(SongQueueSongMapping.COLUMN_ID),
35-
childColumns = arrayOf(SongQueueSongMapping.COLUMN_OG_NEXT_ID),
36-
onDelete = ForeignKey.SET_NULL,
37-
),
3832
],
3933
indices = [
4034
Index(SongQueueSongMapping.COLUMN_QUEUE_ID),
4135
Index(SongQueueSongMapping.COLUMN_IS_HEAD),
4236
Index(SongQueueSongMapping.COLUMN_NEXT_ID),
43-
Index(SongQueueSongMapping.COLUMN_OG_NEXT_ID),
4437
],
4538
)
4639
data class SongQueueSongMapping(
@@ -55,8 +48,6 @@ data class SongQueueSongMapping(
5548
val isHead: Boolean,
5649
@ColumnInfo(COLUMN_NEXT_ID)
5750
val nextId: String?,
58-
@ColumnInfo(COLUMN_OG_NEXT_ID)
59-
val ogNextId: String?,
6051
) {
6152
companion object {
6253
const val TABLE = "song_queue_songs_mapping"
@@ -65,6 +56,5 @@ data class SongQueueSongMapping(
6556
const val COLUMN_SONG_ID = "song_id"
6657
const val COLUMN_IS_HEAD = "is_head"
6758
const val COLUMN_NEXT_ID = "next_id"
68-
const val COLUMN_OG_NEXT_ID = "og_next_id"
6959
}
7060
}

0 commit comments

Comments
 (0)