Enhance library performance, genre separation, and better TTML support#2082
Conversation
2722a8e to
2332d1d
Compare
|
we'd really appreciate using semantic commits |
fb6887a to
9e06a35
Compare
76cba5d to
084ee98
Compare
17a694e to
a84d138
Compare
|
Thanks for the work here, but I can’t merge this PR yet. I validated it against current
Once these are fixed and covered by tests, I’m happy to re-review. |
…th index migration LibraryStateHolder: add distinctUntilChanged() after the theme-color map in the genres flow. The existing one skips recomputation when genre seeds are unchanged; the new one suppresses downstream recomposition when the fully-resolved Genre list is structurally identical — preventing UI invalidation on every unrelated Room table write. Add Migration41To42 to create index_songs_file_path on existing installs. SongEntity already declares the index for fresh installs; this covers devices upgrading from schema v41. getSongByPath() was doing a full table scan — 10–50ms per call at 10 000+ songs on mid-range hardware. Bump PixelPlayDatabase version to 42 and register MIGRATION_41_42.
LyricsImportSecurity: add MAX_TTML_FILE_BYTES (1 MB) separate from the 256 KB LRC limit — Apple Music TTML files routinely exceed 256 KB and were silently rejected. LyricsUtils: extend looksLikeTtmlDocument to match <smil> root elements (Apple Music / broadcast TTML variants) and <tt:tt> namespaced roots in addition to bare <tt>. LyricsRepositoryImpl: check SYNCEDLYRICS and TTML tag keys in addition to LYRICS and UNSYNCEDLYRICS when reading embedded lyrics from file metadata — covers files tagged by mp3tag, Kid3, and broadcast tools.
- Add MusicDao.getSongsByGenreContaining with four LIKE patterns covering all comma-separated positions (first, last, middle, exact) - Fix getGenres() to flatMap raw genre strings on "," before building Genre objects, so "Rock, Pop" appears as two separate genre cards - Fix getMusicByGenre() to use getSongsByGenreContaining instead of the exact-match getSongsByGenre so multi-genre songs are included when browsing any of their genres
Pass Context into formatTimelineLabelForRange and convert am/pm hour labels to 24h format (e.g. "09:00") when the device is set to 24-hour time, leaving 12h format unchanged for 12-hour devices. Covers both the full stats screen (ListeningTimelineSection via TimelineBarChart) and the overview card (MiniListeningTimeline in StatsOverviewCard), which was previously rendering raw entry.label with no formatting.
resolveTimestampBucket was hardcoding "h a" pattern, always producing 12h labels (e.g. "2 PM") regardless of system setting. Now checks AndroidDateFormat.is24HourFormat(context) and uses "HH:mm" pattern (e.g. "14:00") when the device is set to 24-hour time.
…-code removal
DualPlayerEngine.kt
- fix: FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS → FLAG_ENABLE_CONSTANT_BITRATE_SEEKING
VBR MP3s with Xing/VBRI headers now seek via their own table instead of the CBR
approximation, eliminating the ±30 s position jumps on variable-bitrate files
- feat: replace inline DefaultLoadControl with buildAdaptiveLoadControl()
Low-RAM devices (ActivityManager.isLowRamDevice) get 15 s/30 s buffers; normal
devices keep 30 s/60 s; both tiers drop bufferForPlaybackMs 5 000 → 2 500 ms
(ExoPlayer's documented default) cutting first-audio latency with no regression
- refactor: add CLOUD_PROXY_SCHEMES = REMOTE_MEDIA_SCHEMES minus {http, https}
Scopes resolveDataSpec cache lookup and resolveMediaItem guard to proxy schemes only;
plain HTTP/HTTPS URIs previously entered the LruCache path needlessly on every
DataSpec resolution and on every setMediaItem call
- refactor: pre-resolution loop — remove mutableListOf<Uri> + for-each
Replace with two direct ?.takeIf { scheme in CLOUD_PROXY_SCHEMES }?.let { } chains,
eliminating one list allocation and one iterator per track transition
- refactor: rebuildPlayersPreservingMasterState — .map → pre-sized ArrayList + for loop
Removes the IntRange object and avoids the extra copy that .map produces
- refactor: ensureQueueSnapshot — merge two consecutive refresh guards into one
isEmpty() short-circuits the size check so refreshQueueSnapshotFromMaster() is
called at most once per invocation instead of potentially twice
- remove: awaitPlayerPlaying — private, never called anywhere
- fix: disable redundant ID3 parsing for FLAC
MusicService.kt
- fix: remove unreachable `return true` after `return hasWearHints` in
shouldRejectWearController (dead code, flagged by the compiler as unreachable)
- perf: ByteArrayOutputStream in readBytesCapped pre-sized to 4 × DEFAULT_STREAM_BUFFER_SIZE
(32 KB) to cut reallocation churn on typical 50–300 KB album-art payloads
- chore: collapse triple-duplicate KDoc block above the `future` extension function
into a single comment
Promote EqualizerViewMode out of its nested scope into a standalone file, update EqualizerPreferencesRepository and all import sites to use it, and add a missing catch block in DualPlayerEngine.
…n startup Opus files store encoder pre-skip samples in an edit list. Setting FLAG_WORKAROUND_IGNORE_EDIT_LISTS on the Mp4Extractor caused ExoPlayer to discard that edit list, landing playback ~44-52s into the track on first startup. The skip only happened on the first 1-2 tracks because the audio offload stall fallback (4s timer) would trigger rebuildPlayersPreservingMasterState, which snapshotted the bad position and restored it on the rebuilt player. Once offload was disabled after the second rebuild, it never fired again. Fix 1: Remove FLAG_WORKAROUND_IGNORE_EDIT_LISTS. That flag exists for broken MP4 files with malformed edit lists and actively harms Opus. Fix 2: Guard the position snapshot in rebuildPlayersPreservingMasterState to fall back to 0 if the saved position is under 5s, preventing any future early-startup rebuild from restoring a spurious offset. Also removes the now-unused Mp4Extractor import.
…te re-crossfade After a crossfade swap, swapListener was calling scheduleTransitionFor immediately on the incoming track. The duration wait loop would block on C.TIME_UNSET, but by the time duration resolved, currentPosition had already advanced past transitionPoint — triggering an instant second crossfade and jumping the track ~47s into playback. Add a 1s delay before scheduling so the incoming track's duration and position stabilize first. A currentObservedPlayer identity check ensures the delayed schedule is a no-op if another swap occurs in the interim.
The root cause of premature song termination and massive position skips (jumping forward ~49s at a time) is a HAL-level bug in the audio offload implementation on the Pixel (akita_beta) running Android SDK 37 beta. ExoPlayer is merely reporting the corrupt hardware position counters. Fix this by adding Google/Pixel to `shouldDisableAudioOffloadByDefaultForDevice` for SDK >= 37, mirroring the existing Xiaomi workaround for SDK >= 36. Note: Retain the previous TransitionController 1s delay fix as it provides a genuine improvement. This workaround should be revisited once stable SDK 37 releases to check if the HAL bug was resolved upstream.
… transition logic MusicDao.kt: Updated genre filtering to use LIKE for case-insensitive matching, ensuring consistent results for all genre list formats. DualPlayerEngine.kt: Enabled setPrioritizeTimeOverSizeThresholds(true) to utilize duration-based buffering, preventing premature rebuffering during high-bitrate FLAC playback. TransitionController.kt: Added a mediaId check post-delay to prevent stale transitions if the user switches tracks while the timer is running.
….4.0 incompatibility Rebase introduced multiple duplicate declaration blocks in UserPreferencesRepository and duplicate init block in SettingsViewModel, causing overload resolution ambiguity errors across SettingsViewModel, SetupViewModel, FileExplorerStateHolder, LibraryStateHolder, MainViewModel, PlayerViewModel, and DailyMixStateHolder. Fixes: - Remove duplicate libraryTabsOrderFlow declaration (lines 1329 vs 1046) - Remove two old-style dataStore.data.map blocks duplicating blockedDirectoriesFlow, initialSetupDoneFlow, keepPlayingInBackgroundFlow, favoriteSongIdsFlow, fullPlayerLoadingTweaksFlow, showPlayerFileInfoFlow, minSongDurationFlow, minTracksPerAlbumFlow, isGenreGridViewFlow, isAlbumsListViewFlow, and more - Remove duplicate dailyMixSongIdsFlow, yourMixSongIdsFlow, lastDailyMixUpdateFlow and their save functions - Merge duplicate init block in SettingsViewModel - Remove duplicate KDoc comment in SetupViewModel - Pin Kotlin to 2.3.20 in CodeQL workflow via gradle.properties at CI runtime; CodeQL's extractor does not yet support Kotlin 2.4.0 TODO: remove the Kotlin version pin once CodeQL adds Kotlin 2.4.0 support.
No description provided.