Desktop client + shared core/data layer - #17
Draft
LizAinslie wants to merge 69 commits into
Draft
Conversation
Split shared playback/library/path code into :core (android + jvm). :desktop is Material 3 + LibVLC (vlcj), scans OS music folders, and talks MPRIS / media keys. :app depends on :core.
Shared Compose UI for cover art, album pages, and player chrome. Desktop: right now-playing/queue/history sidebar, bottom transport. Album pages split cover + tracks from 600dp up (tablet/desktop).
Match Android color tokens, paint the window/shell background, and tint the sidebar plus transport from the playing cover (folder art or embedded tags).
Yuri chrome uses onBackground/onSurface. Cover colors stay on the right now-playing pane. LibVLC runs on its own thread with a file path MRL so clicks actually play; missing VLC is reported in the UI. Do not use LWJGL for this — OpenAL does not decode FLAC/MP3.
Download official VideoLAN natives at build (Windows/macOS NuGet, Debian libvlc on Linux) and point JNA at them before the engine starts. System VLC is still used when no bundle is present.
Linux installers depend on distro vlc instead of vendoring LibVLC. Windows and macOS installers still bundle official VideoLAN natives.
Album pages both paint through AlbumArtBackdrop so the cover wash is the same helper: a soft radial bloom instead of a hard vertical band.
Left bento nav, center stage, right now-playing plus shared QueuePanel, bottom bar play button tinted from the current cover. Desktop settings is a large Discord-style modal. Android 12+ can follow system colors. Room uses BundledSQLiteDriver so the catalog is on the KMP sqlite path.
:app now depends on :components so AlbumArtBackdrop and theme types compile. Desktop gets the bento layout, resizable sidebars, extra local folders, and Jellyfin/Subsonic sources. Phone nav is Home / My Stuff / Search with a floating art-tinted mini player.
HomeRow's slot was typed as Row, which is a composable not a receiver. Use RowScope, and pass AlbumItem/ArtistItem display strings.
Drop the 380dp nested LazyVerticalGrid so pins sit at the top of the page and the whole Home list is the only scroller.
Bottom nav and the top bar share those names. Drop the extra Catalog and My Stuff headlines so the tab title is not doubled.
The Explore tab uses a pill search field instead of a title. A Filters row under it opens a sheet to pick local and remote libraries.
Pins are just pins — no radio control sitting above the grid.
Home playlist tiles use real playlist art (seed tracks from the catalog). Desktop gets playlists with secret/public covers, pan-zoom crop, queue drag-reorder, and song/album tag editing.
Route those screens on a per-tab back stack so back (and a re-tap of the current tab) returns to Home, My Stuff, or Explore — whichever opened them. Enable Android predictive back so the overlay scales off the launching tab instead of replacing it.
Drag was adding each pointer delta to a stale fraction and then rescaling both panes, so the handles fought the cursor. Accumulate the gesture in pixels, start immediately (no touch slop), and only persist on release. Wider hit target and a visible grip.
Providers is its own settings category: list, add Jellyfin or Subsonic, then the same identity / connection / enabled / save / delete editor. Test connection is a real button on each editor (and on each saved server in the list).
Saving a Jellyfin or Subsonic provider now kicks off a forced first scan of that server (queued if another sync is already running). Desktop does the same for a new remote or extra folder instead of waiting on a full library rescan.
Filter chips in the desktop rail scroll horizontally so Artists no longer wraps. Album art frames stay square; non-square images keep their ratio and sit centered in that square.
Queue / Recently played chips scroll instead of wrapping. A TravelExplore control next to settings shows scan progress and per-source pause/stop/resume, matching mobile. Settings is a quiet icon instead of a filled accent button.
Partial scan skips already-indexed libraries and only pulls new Jellyfin/Subsonic tracks. Full scan walks everything. Pause and stop cancel the in-flight source at a page boundary and can target all libraries or a single one.
Search uses a 40dp centered pill so the query is no longer clipped by OutlinedTextField padding. Home opens with a greeting and up to eight My Stuff shortcuts in a 2×4 grid (Liked Songs, pins, then recents), followed by Jump back in / playlists / albums / artists.
Search now finds songs, albums, artists, and playlists from the index plus live Jellyfin/Subsonic queries. Library filters sit as chips (All, On this device, each server) instead of a sheet.
Persist the track index and per-source cursors so a scan survives restart. Partial scans skip warm libraries (or delta if the server grew); full scans resume from the last page and drop deleted tracks. Pause/stop checkpoint the cursor instead of starting over.
Match on path/folder names, fold stylized letters (ø → o), and require query tokens instead of a raw substring. Local hits render immediately so a slow Jellyfin search cannot hide on-disk results.
The 8-up grid no longer fills with recents or random library albums. Pins are liked songs plus actual pins; album/artist rows only use that same collection.
Shuffle and repeat (off / all / one) live entirely in PlayerSession so engines only play audio. Repeat-all wraps; shuffle keeps the current track and restores original order. Seek bar sits below the transport with a gap. Artist pages follow the Spotify layout (banner, popular, you liked, discography chips, appears on, about).
Desktop artist pages resolve the same remote sources as mobile: Wikipedia extract + TheAudioDB fanart/banner. Headers can be picked from those results, set from a local file, or cleared. The About card shows the fetched bio.
Artist pages now merge Wikipedia, TheAudioDB, Deezer, MusicBrainz, Wikidata, plus Jellyfin/Navidrome art. About lists genres. The left rail is only My Stuff (pins, likes, playlists). New playlists get a name/description dialog and land in the collection.
Source tiles are grouped and clipped instead of stacked 1:1 in a 3:1 grid. The crop canvas stays inside the panel. Cropped headers write a unique .jpg so Coil actually reloads them.
Aspect no longer drives layout — every tile is the same size and cropped to fit, grouped by source so they cannot overlap.
Search hits (Subsonic/Jellyfin) are upserted into the library like mobile ensureTracksPresent, stored under a cross-source catalog key, and kept across rescans so Add to playlist actually shows.
Sidebar no longer stacks extra covers and a second heart. Queue keeps the now-playing row; its art arrow grows a 1:1 cover above the details. Bottom tray spacing matches the sidebar gutters.
Queue, position, shuffle, and repeat save like Android and restore paused. Playlists keep a copy of each track so Subsonic/search adds still render after a restart even if the index missed them.
Clancy (and any local+Jellyfin+Navidrome album) is one row per song. Cloud and explicit badges open a sources picker; the chosen source is saved and used for playback, same as Android.
VLC no longer resets to 100% on every load. The tray slider is the app volume, MPRIS Volume get/set matches it, and play/pause/ next/prev/volume keys work from the OS (playerctl, media keys).
Save and restore the playhead (VLC ignored setTime until play). Playlist pages include snapshot tracks even when stored ids drifted. Opening sources no longer scans the whole library on the UI thread.
Reorder is drag, remove is the context menu.
Search vs scan rows now share a loose title+album key, playlist snapshots are re-seeded after every source replace, and Done always upserts the live Track. Grep stderr for YuriPlayer.Playlist or YuriPlayer.Index.
Add to queue now lands in the hot lane (plays next, before the album rest) using the same QueuePanel sections as mobile. VLC actually starts the prepared next track on end. Playlist members render in add order from snapshots so Navidrome rows stay visible. Hearts save to My Stuff without pinning home.
Context menus listen on the initial pointer pass so clicks aren't eaten by the player bar or drag-to-reorder. Same song menu as everywhere else, including the bottom-left now playing chip.
Members are stored as ordered entries (ordinal + snapshot). Adds append at the end, drag-reorder rewrites ordinals, and live library metadata no longer reshuffles the list.
VLC now starts with media().play instead of prepare-then-pause. Stream URLs are minted at play time so Navidrome/Jellyfin tokens are fresh after restart, and the hot/cold queue is what we persist.
Restore no longer prepare-pauses the engine (that hung VLC so play/skip never ran). Play and skip both loadCurrent then media().play, reminting stream URLs. Pause only pauses.
Claim org.mpris.MediaPlayer2.yuriplayer on a private connection, return Variants from Properties.Get (what playerctl/GNOME expect), and grab GNOME media keys as a fallback.
Installs yuriplayer.desktop + hicolor icons, sets WM_CLASS, and announces MPRIS Identity so GNOME/KDE media indicators can list us.
VLC's HTTP/2 stack was dying with Protocol error and poisoning
every later play. Recycle the player on error.
yuriLog { } is the common DSL: logcat on Android, slf4j/logback
on desktop (Darwin os_log later). Tokens are redacted.
Replace android.util.Log and leftover printStackTrace with the KMP yuriLog DSL. AlbumLog and PlaylistLog stay as domain helpers on top of the same facade. Tags are short names; platforms prefix YuriPlayer.
…a layer onto it; fix multi-source discography/artist dedupe. Desktop: migrate Track→Song, keep JSON index for now.
Fork the upstream dev.toastbits:mediasession library into :mediasession so we can ship MPRIS/SMTC fixes without waiting on upstream releases. This is not a dependency swap — it is intentionally vendored. Local changes over upstream 0.1.1: - Convert MPRIS Seek/SetPosition microseconds to milliseconds before invoking onSeek/onSetPosition (PlayerInterface + SessionInterface). - Add the onSetVolume callback (upstream omitted it) so volume changes made from a media key / playerctl round-trip to the app. - Normalize Volume/Rate as Float in the commonMediaSession API while marshalling to Double on the DBus wire. - Keep the commonMain/jvmMain split so the API surface is platform-agnostic (future Darwin target) and the dbus-java + JNA/SMTC bindings stay JVM-only. Upstream: dev.toastbits:mediasession 0.1.1
vlcj only searches /usr/lib*. NixOS keeps libvlc.so in the store and exposes it via /run/current-system/sw (IntelliJ PATH often omits that). Parse wrapProgram scripts, set VLC_PLUGIN_PATH via setenv, and register a vlcj DiscoveryDirectoryProvider so native init succeeds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the Compose Desktop client online and moves the shared data model, Room
catalog, and playback primitives into
:coreso Android and desktop share onesource of truth.
Highlights
Desktop client (
:desktop, new)pages, now-playing bar + sidebar queue, context menus, metadata editing,
provider (Jellyfin / Subsonic / local) management.
vlcj) with bundled natives for Windows/macOS; Linuxpackages depend on distro
vlc.:mediasessionfork, plus macOS Now Playing stubs.
DesktopIndexStore) + per-source scan checkpoints, so alarge Jellyfin/Navidrome walk survives restart.
Shared UI (
:components, new)player bar, now-playing sidebar, context-menu DSL (prediction-cone submenus),
theme, and settings rows.
Shared core (
:core, expanded)Song/AlbumItem/ArtistItem/credits/identity model.YuriDatabase,CatalogDao, entities, migrations) with anexpect/actual@ConstructedByconstructor for Android + JVM.CatalogRepositoryand library sources (Jellyfin, Subsonic, source registry)moved out of
:app.PlaybackEngineSPI,PlayerSessionqueue host, radio algorithms, andplatform seams (
appDirectories,hostOs,OsMediaControls, logging).Android
:componentsUI and sharedCatalogRepository(behind a
LocalScannerseam for MediaStore/SAF).Vendored
:mediasessiondev.toastbits:mediasession(0.1.1) with MPRIS seek-offset,onSetVolume, and Float/Double marshalling fixes not yet upstream.Infra
tasks, deb/rpm/dmg/pkg/msi/exe targets.
Fixed along the way
Navidrome) seed the artist page from all sources.