Skip to content

Commit eb4b18a

Browse files
Merge remote-tracking branch 'upstream/main'
2 parents da141cb + 930d97a commit eb4b18a

8 files changed

Lines changed: 251 additions & 58 deletions

File tree

app/src/main/assets/player_configs.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
"f5fbaad8": { "sig": "ST(1,8150,INPUT)", "nClass": "oO", "sts": 20634, "aliases": ["91dcbffc"] },
7474
"a9fe4815": { "sig": "ST(1,8150,INPUT)", "nClass": "oO", "sts": 20634, "aliases": ["6a33e5c2"] },
7575
"13792696": { "sig": "ST(1,8150,INPUT)", "nClass": "oO", "sts": 20634, "aliases": ["5ff90ebf"] },
76-
"6009b507": { "sig": "ST(1,8150,INPUT)", "nClass": "oO", "sts": 20634, "aliases": ["4eab840f"] }
76+
"6009b507": { "sig": "ST(1,8150,INPUT)", "nClass": "oO", "sts": 20634, "aliases": ["4eab840f"] },
77+
"f551602d": { "sig": "vR(5,6110,INPUT)", "nClass": "Vg", "sts": 20639, "aliases": ["f474e1c9"] },
78+
"74ac8690": { "sig": "vR(5,6110,INPUT)", "nClass": "Vg", "sts": 20639, "aliases": ["42275f80"] },
79+
"5fa5b166": { "sig": "C3(15,933,INPUT)", "nClass": "WM", "sts": 20640, "aliases": ["605c82cc"] }
7780
}
7881
}

app/src/main/kotlin/com/music/echo/db/MusicDatabase.kt

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class MusicDatabase(
149149
AutoMigration(from = 25, to = 26),
150150
AutoMigration(from = 26, to = 27),
151151
AutoMigration(from = 27, to = 28),
152-
AutoMigration(from = 28, to = 29),
152+
153153
AutoMigration(from = 30, to = 31),
154154
AutoMigration(from = 31, to = 32),
155155
AutoMigration(from = 32, to = 33),
@@ -179,6 +179,7 @@ abstract class InternalDatabase : RoomDatabase() {
179179
MIGRATION_22_24,
180180
MIGRATION_24_25,
181181
MIGRATION_27_28,
182+
MIGRATION_28_29,
182183
MIGRATION_29_30,
183184
MIGRATION_36_37,
184185
MIGRATION_37_38,
@@ -804,8 +805,10 @@ val MIGRATION_27_28 =
804805
db.execSQL("INSERT INTO `_new_album` (`id`,`playlistId`,`title`,`year`,`thumbnailUrl`,`themeColor`,`songCount`,`duration`,`explicit`,`lastUpdateTime`,`bookmarkedAt`,`likedDate`,`inLibrary`,`isLocal`,`isUploaded`) SELECT `id`,`playlistId`,`title`,`year`,`thumbnailUrl`,`themeColor`,`songCount`,`duration`,`explicit`,`lastUpdateTime`,`bookmarkedAt`,`likedDate`,`inLibrary`,`isLocal`,`isUploaded` FROM `album`")
805806
db.execSQL("DROP TABLE `album`")
806807
db.execSQL("ALTER TABLE `_new_album` RENAME TO `album`")
807-
db.execSQL("CREATE TABLE IF NOT EXISTS `_new_playlist` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `browseId` TEXT, `createdAt` INTEGER, `lastUpdateTime` INTEGER, `isEditable` INTEGER NOT NULL DEFAULT true, `bookmarkedAt` INTEGER, `remoteSongCount` INTEGER, `playEndpointParams` TEXT, `thumbnailUrl` TEXT, `shuffleEndpointParams` TEXT, `radioEndpointParams` TEXT, `isLocal` INTEGER NOT NULL DEFAULT false, PRIMARY KEY(`id`))")
808+
db.execSQL("CREATE TABLE IF NOT EXISTS `_new_playlist` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `browseId` TEXT, `createdAt` INTEGER, `lastUpdateTime` INTEGER, `isEditable` INTEGER NOT NULL DEFAULT true, `bookmarkedAt` INTEGER, `remoteSongCount` INTEGER, `playEndpointParams` TEXT, `thumbnailUrl` TEXT, `shuffleEndpointParams` TEXT, `radioEndpointParams` TEXT, `isLocal` INTEGER NOT NULL DEFAULT false, `isAutoSync` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))")
808809
db.execSQL("INSERT INTO `_new_playlist` (`id`,`name`,`browseId`,`createdAt`,`lastUpdateTime`,`isEditable`,`bookmarkedAt`,`remoteSongCount`,`playEndpointParams`,`thumbnailUrl`,`shuffleEndpointParams`,`radioEndpointParams`,`isLocal`) SELECT `id`,`name`,`browseId`,`createdAt`,`lastUpdateTime`,`isEditable`,`bookmarkedAt`,`remoteSongCount`,`playEndpointParams`,`thumbnailUrl`,`shuffleEndpointParams`,`radioEndpointParams`,`isLocal` FROM `playlist`")
810+
811+
db.execSQL("UPDATE `_new_playlist` SET `isAutoSync` = 0")
809812
db.execSQL("DROP TABLE `playlist`")
810813
db.execSQL("ALTER TABLE `_new_playlist` RENAME TO `playlist`")
811814
db.execSQL("CREATE VIEW `sorted_song_artist_map` AS SELECT * FROM song_artist_map ORDER BY position")
@@ -814,6 +817,25 @@ val MIGRATION_27_28 =
814817
}
815818
}
816819

820+
val MIGRATION_28_29 =
821+
object : Migration(28, 29) {
822+
override fun migrate(db: SupportSQLiteDatabase) {
823+
var columnExists = false
824+
db.query("PRAGMA table_info(playlist)").use { cursor ->
825+
val nameIndex = cursor.getColumnIndex("name")
826+
while (cursor.moveToNext()) {
827+
if (nameIndex >= 0 && cursor.getString(nameIndex) == "isAutoSync") {
828+
columnExists = true
829+
break
830+
}
831+
}
832+
}
833+
if (!columnExists) {
834+
db.execSQL("ALTER TABLE playlist ADD COLUMN isAutoSync INTEGER NOT NULL DEFAULT 0")
835+
}
836+
}
837+
}
838+
817839
val MIGRATION_36_37 =
818840
object : Migration(36, 37) {
819841
override fun migrate(db: SupportSQLiteDatabase) {

app/src/main/kotlin/com/music/echo/engine/EchoBrainEngine.kt

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -202,25 +202,55 @@ class EchoBrainEngine @Inject constructor(
202202
}
203203
}
204204

205+
// 4. Existing upcoming Echo Brain tracks
206+
val upcomingBrainIndices = mutableListOf<Int>()
207+
val upcomingBrainMetadatas = mutableListOf<MediaMetadata>()
208+
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
209+
val player = conn.player
210+
for (i in player.currentMediaItemIndex + 1 until player.mediaItemCount) {
211+
val item = player.getMediaItemAt(i)
212+
if (item.metadata?.source == QueueItemSource.ECHO_BRAIN) {
213+
upcomingBrainIndices.add(i)
214+
item.metadata?.let { upcomingBrainMetadatas.add(it) }
215+
}
216+
}
217+
}
218+
205219
// Await all sources
206220
val anchorCandidates = anchorDeferred.await()
207221
val momentumCandidates = momentumDeferred.await()
208222
val vaultCandidates = vaultDeferred.await()
209223

210-
val allCandidates = (anchorCandidates + momentumCandidates + vaultCandidates).distinctBy { it.id }
224+
val allCandidates = (anchorCandidates + momentumCandidates + vaultCandidates + upcomingBrainMetadatas).distinctBy { it.id }
211225

212226
if (allCandidates.isNotEmpty()) {
213-
val recentSongs = buildSet {
214-
for (i in 0 until conn.player.mediaItemCount) {
215-
conn.player.getMediaItemAt(i).metadata?.id?.let { add(it) }
227+
val recentSongs = buildSet {
228+
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
229+
for (i in 0..conn.player.currentMediaItemIndex) {
230+
conn.player.getMediaItemAt(i).metadata?.id?.let { add(it) }
231+
}
232+
}
216233
}
217-
}
218-
219-
val ranked = neuroEngine.rank(allCandidates, recentSongs)
220-
// Take top 3 for the "Runway"
221-
val topCandidates = ranked.take(3)
222-
223-
val itemsToInject = topCandidates.map { it ->
234+
235+
val ranked = neuroEngine.rank(allCandidates, recentSongs)
236+
// Take top 15 for the dynamic queue
237+
val topCandidates = ranked.take(15)
238+
val topIds = topCandidates.map { it.id }.toSet()
239+
240+
// Identify which existing ECHO_BRAIN tracks scored poorly and should be pruned
241+
val indicesToRemove = mutableListOf<Int>()
242+
for (i in upcomingBrainIndices.indices) {
243+
if (upcomingBrainMetadatas[i].id !in topIds) {
244+
indicesToRemove.add(upcomingBrainIndices[i])
245+
}
246+
}
247+
248+
// Identify completely new tracks to inject
249+
val existingUpcomingIds = upcomingBrainMetadatas.map { it.id }.toSet()
250+
val newCandidatesToInject = topCandidates.filter { it.id !in existingUpcomingIds }
251+
252+
// Shuffle new tracks to place them randomly and keep the flow organic
253+
val itemsToInject = newCandidatesToInject.shuffled().map { it ->
224254
val newMeta = MediaMetadata(
225255
id = it.id,
226256
title = it.title,
@@ -234,13 +264,25 @@ class EchoBrainEngine @Inject constructor(
234264
newMeta.toMediaItem()
235265
}
236266

237-
if (itemsToInject.isNotEmpty()) {
238-
// Inject the runway batch with a delay to prevent Media3 PlaybackStatsListener crash
239-
kotlinx.coroutines.delay(1500)
240-
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
241-
conn.player.addMediaItems(conn.player.currentMediaItemIndex + 1, itemsToInject)
267+
// Apply queue modifications on the Main thread
268+
kotlinx.coroutines.delay(1500) // Delay to prevent Media3 PlaybackStatsListener crash
269+
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Main) {
270+
val player = conn.player
271+
272+
// Remove irrelevant tracks (reverse order to maintain index integrity)
273+
indicesToRemove.reversed().forEach { index ->
274+
if (index < player.mediaItemCount) {
275+
player.removeMediaItem(index)
276+
}
277+
}
278+
279+
// Inject new relevant tracks
280+
if (itemsToInject.isNotEmpty()) {
281+
player.addMediaItems(player.currentMediaItemIndex + 1, itemsToInject)
282+
repository.logActivity("Queued", "Pruned ${indicesToRemove.size} irrelevant tracks, added ${itemsToInject.size} new random tracks to runway")
283+
} else if (indicesToRemove.isNotEmpty()) {
284+
repository.logActivity("Pruned", "Pruned ${indicesToRemove.size} irrelevant tracks to maintain flow")
242285
}
243-
repository.logActivity("Queued", "Added ${itemsToInject.size} tracks to runway queue")
244286
}
245287
}
246288
}

app/src/main/kotlin/com/music/echo/ui/component/Lyrics.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,8 @@ fun Lyrics(
10331033
isSelectionModeActive = isSelectionModeActive,
10341034
isSelected = isSelected,
10351035
expressiveAccent = expressiveAccent,
1036+
lyricsTextSize = lyricsTextSize,
1037+
lyricsLineSpacing = lyricsLineSpacing,
10361038
onClick = {
10371039
if (isSelectionModeActive) {
10381040
if (isSelected) {

app/src/main/kotlin/com/music/echo/ui/component/MetroLyrics.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ fun MetroLyricsLine(
127127
isSelectionModeActive: Boolean,
128128
isAutoScrollActive: Boolean,
129129
expressiveAccent: Color,
130+
lyricsTextSize: Float = 36f,
131+
lyricsLineSpacing: Float = 1.3f,
130132
modifier: Modifier = Modifier
131133
) {
132134
val (appleMusicLyricsBlur) = rememberPreference(AppleMusicLyricsBlurKey, true)
@@ -182,9 +184,6 @@ fun MetroLyricsLine(
182184
label = "lineAlpha"
183185
)
184186

185-
val lyricsTextSize = 36f
186-
val lyricsLineSpacing = 1.3f
187-
188187
val itemModifier = modifier
189188
.fillMaxWidth()
190189
.clip(RoundedCornerShape(16.dp))

app/src/main/kotlin/com/music/echo/ui/screens/HomeScreen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@ fun HomeScreen(
814814
accountPlaylists,
815815
forgottenFavorites,
816816
communityPlaylists,
817+
echoBrainPlaylists,
817818
similarRecommendations,
818819
homePage?.sections,
819820
explorePage?.moodAndGenres
@@ -822,6 +823,7 @@ fun HomeScreen(
822823

823824
if (showSpeedDial && speedDialItems.isNotEmpty()) list.add(HomeSection.SpeedDial)
824825
if (quickPicks?.isNotEmpty() == true) list.add(HomeSection.QuickPicks)
826+
if (echoBrainPlaylists?.isNotEmpty() == true) list.add(HomeSection.EchoBrainPlaylists)
825827
if (communityPlaylists?.isNotEmpty() == true) list.add(HomeSection.FromTheCommunity)
826828
if (dailyDiscover?.isNotEmpty() == true) list.add(HomeSection.DailyDiscover)
827829
if (keepListening?.isNotEmpty() == true) list.add(HomeSection.KeepListening)
@@ -855,6 +857,7 @@ fun HomeScreen(
855857
HomeSection.KeepListening,
856858
HomeSection.AccountPlaylists,
857859
HomeSection.ForgottenFavorites,
860+
HomeSection.EchoBrainPlaylists,
858861
HomeSection.FromTheCommunity -> 300
859862

860863
else -> 100
@@ -873,6 +876,7 @@ fun HomeScreen(
873876
HomeSection.KeepListening,
874877
HomeSection.AccountPlaylists,
875878
HomeSection.ForgottenFavorites,
879+
HomeSection.EchoBrainPlaylists,
876880
HomeSection.FromTheCommunity -> sectionRandom.nextInt(-100, 400)
877881

878882

@@ -884,6 +888,7 @@ fun HomeScreen(
884888
val defaultOrder = mapOf(
885889
HomeSection.QuickPicks to 1000,
886890
HomeSection.SpeedDial to 100,
891+
HomeSection.EchoBrainPlaylists to 85,
887892
HomeSection.FromTheCommunity to 80,
888893
HomeSection.DailyDiscover to 70,
889894
HomeSection.KeepListening to 60,

app/src/main/kotlin/com/music/echo/ui/screens/library/LibraryPlaylistsScreen.kt

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -211,21 +211,6 @@ fun LibraryPlaylistsScreen(
211211
backStackEntry?.savedStateHandle?.set("scrollToTop", false)
212212
}
213213
}
214-
215-
var showCreatePlaylistDialog by rememberSaveable { mutableStateOf(false) }
216-
217-
if (showCreatePlaylistDialog) {
218-
CreatePlaylistDialog(
219-
onDismiss = { showCreatePlaylistDialog = false },
220-
initialTextFieldValue = initialTextFieldValue,
221-
allowSyncing = allowSyncing,
222-
onPlaylistCreated = { playlistId ->
223-
showCreatePlaylistDialog = false
224-
navController.navigate("local_playlist/$playlistId")
225-
}
226-
)
227-
}
228-
229214
val headerContent = @Composable {
230215
Row(
231216
verticalAlignment = Alignment.CenterVertically,
@@ -382,14 +367,6 @@ fun LibraryPlaylistsScreen(
382367
}
383368
}
384369
}
385-
386-
HideOnScrollFAB(
387-
lazyListState = lazyListState,
388-
icon = R.drawable.add,
389-
onClick = {
390-
showCreatePlaylistDialog = true
391-
},
392-
)
393370
}
394371

395372
LibraryViewType.GRID -> {
@@ -516,14 +493,6 @@ fun LibraryPlaylistsScreen(
516493
}
517494
}
518495
}
519-
520-
HideOnScrollFAB(
521-
lazyListState = lazyGridState,
522-
icon = R.drawable.add,
523-
onClick = {
524-
showCreatePlaylistDialog = true
525-
},
526-
)
527496
}
528497
}
529498
}

0 commit comments

Comments
 (0)