CHANGELOG.md
106 lines · 13.4 KB
Changed in: 0.5.0
| 1 | # Changelog |
| 2 | |
| 3 | All notable changes to Earhorn Music for GNU/Linux. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). |
| 4 | |
| 5 | ## [0.5.0] — 2026-11-01 |
| 6 | |
| 7 | Result of a five-team review (Android parity, correctness, security/privacy, UX/accessibility) against Earhorn Android 0.4.23. |
| 8 | |
| 9 | ### Added |
| 10 | - **Rescan library on a MinimServer** from the toolbar Refresh button, with progress, a result message and a return to the browse root afterwards. "Refresh devices" (network search) is the other choice in the same menu. Ported from Android. |
| 11 | - **Refresh what's playing** on the "Following …" badge: re-asks the Cast receiver and re-reads musicnow's `/status` immediately. |
| 12 | - **Album notes** (ID3 COMMENT / Vorbis DESCRIPTION, then sibling `.txt` / `review.txt` / `info.txt` / `notes.txt`), **search history** (last 10), **choose which details Now Playing shows** (metadata field toggles), **delete a saved playlist**. These were in the README but not in the code. |
| 13 | - Follow mode: "Connecting to …" / "Waiting for …" when the followed session is starting or has gone quiet; a **Stop following** button on the transport bar; "Connecting…" on the picked row. |
| 14 | - Messages instead of silence: "Nothing to play there", "Couldn't load tracks from the server", "Added … to the queue", "Couldn't play ‘…’ — skipped", "No servers found". |
| 15 | - Browse: Retry button; automatic retry with a network re-search while the server is still being discovered. |
| 16 | - Search results are grouped (albums, artists, tracks …) with counts, and each group opens its matches. |
| 17 | - "More from this album" on Now Playing lists that album's tracks in disc and track order. |
| 18 | - Following a Cast group reconnects at its last known address when discovery can't find it after 20 s. |
| 19 | - Live streams show LIVE instead of a duration, and the progress bar stays still. |
| 20 | |
| 21 | ### Changed |
| 22 | - **Party Mode entry point** is now a visible "Party" pill at the end of the breadcrumb row. The old invisible click zone covered the right half of the breadcrumbs and could start Party Mode from a stray crumb click. |
| 23 | - Following a Cast group now pauses local playback (as on Android). |
| 24 | - Menu shortcuts use Ctrl instead of Super; the search pill reads "Ctrl K"; Esc and Alt+← close an open menu or dialog before going back. |
| 25 | - Saved playlists keep cover art, album, year and track number (Android's extra lines). |
| 26 | - Theme: quiet text and outlines meet contrast minimums; menus, dialogs and toasts use the warm palette; shuffle/repeat state no longer depends on colour alone. |
| 27 | - Earhorn's published renderer only accepts http(s) URLs to non-loopback hosts, and tells the controller when it refuses one. Event subscriptions are accepted only with callbacks to the subscriber itself. |
| 28 | - XML parsing limits are set before the network stack starts; external DTDs are refused. |
| 29 | - Volume is saved only while this computer is the renderer, so a remote device's volume no longer replaces it. |
| 30 | |
| 31 | ### Fixed |
| 32 | - musicnow station overlay: a failed read no longer blanks the song; polling backs off when musicnow is busy, stops for library playback and while the session is dead, and restarts when the station or address changes. |
| 33 | - Pause no longer toggles (a second Pause resumed playback); Stop is a real stop. ReplayGain scales the volume slider instead of replacing it. |
| 34 | - Remote UPnP renderers: the queue continues past track 1; Next/Previous, repeat and shuffle work; launch no longer replaces what a saved renderer is playing. |
| 35 | - Follow: dead connections are detected; a moved group leader is followed instead of dropped; the previous song no longer shows while the next is looked up. |
| 36 | - Browse: a slow load can no longer replace a newer folder; errors no longer leave a spinner forever; servers without Search no longer hang Browse; every Play action asks "Stop following and play here?" while following. |
| 37 | - Settings saves can no longer lose writes; config, session and cache are owner-only. |
| 38 | - Lyrics: a slow lookup for the previous song can no longer replace the current one; title variants ("(Remaster)", "[live]") are retried; the sidecar `.lrc` fetch respects the external-calls switch. |
| 39 | - Cast sender: switching devices keeps the new session; stream proxy bounded and bound toward the Cast device only. |
| 40 | - Shutdown stops everything that calls into libvlc before releasing it, and saves the queue on SIGTERM. |
| 41 | |
| 42 | ## [0.2.0] — Cast: sender + observer |
| 43 | |
| 44 | Earhorn desktop can now drive Chromecast and Chromecast Audio devices, and — new for any Earhorn product — *follow* a Cast session that another sender (typically the Earhorn phone app) is already driving on the LAN, mirroring its metadata, artwork, and transport on the desktop without taking over playback. This retires two of the 0.1.0 known limitations ("No Cast sender", "No remote-renderer control yet"). |
| 45 | |
| 46 | ### Added |
| 47 | |
| 48 | - **Cast sender mode.** Pick a Chromecast / Chromecast Audio / speaker group from the renderer picker (cast icon) to stream the local queue to it. Built on chromecast-java-api-v2; the local queue stays the source of truth and `CastRenderer` plugs in behind the existing `Renderer` interface, alongside `VlcjLocalRenderer`. |
| 49 | - **Observer ("Follow") mode.** Pick a device's eye icon in the picker to follow whatever another sender is playing on it. A zero-dependency raw Cast-protocol client (`RawCastClient`: pure JDK TLS + a hand-rolled CastMessage protobuf codec + org.json for payload parsing) reads the receiver's `MEDIA_STATUS` stream directly — no Cast SDK, no Play Services. The desktop renders the followed track's title, artist, album, and art, and its transport controls drive the followed session, all without disturbing local playback. Follow is a passive overlay: closing the window leaves the music playing on the device. |
| 50 | - **Observed-track metadata enrichment.** When following, the observed track is matched against the preferred UPnP server by title/artist/album and merged with the local result to fill in technical fields the Cast wire protocol doesn't carry (year, MIME type, sample rate, bit depth, track/disc number). |
| 51 | - **Combined picker rows.** Each Chromecast device shows both actions inline: cast-to on the row body, follow on a trailing eye button. No more scrolling past a duplicate device list on Cast-heavy networks. A "Stop following" entry appears while a follow session is active. |
| 52 | - **MPRIS + lyrics follow the observed session.** The GNOME/Ubuntu top-bar indicator, multimedia keys, and the lyrics overlay all track the followed Cast track while observing. |
| 53 | |
| 54 | ### Fixed |
| 55 | |
| 56 | - **Receiver metadata now renders in sender mode.** `CastRenderer` previously ignored `media.metadata` on incoming `MEDIA_STATUS`, on the assumption that the local queue was always the source of truth. That left the gramophone placeholder showing over audible playback in three real cases: cold-start auto-reattach with an empty queue, a phone driving the same receiver, and post-`clearQueue` while the receiver kept playing. It now synthesizes the current track from receiver metadata whenever the local queue can't supply a richer answer, preserving the local-queue track (with its fuller DIDL metadata) when the receiver is merely echoing back our own load. |
| 57 | - **Faster metadata on reattach.** A short burst of explicit status requests fires on attach, so metadata populates within roughly half a second instead of waiting for the receiver's next steady-state broadcast. |
| 58 | - **Volume no longer flashes 100%.** On both sender switch and observer attach, the volume meter is seeded from the outgoing renderer / gated until the device reports its actual level — eliminating the "meter jumps to 100% then snaps to the real value" transition, and the audible jump from clicking a slider that hadn't synced yet. |
| 59 | - **Volume control visible without a queue.** The slider now renders whenever the local queue is empty (casting or observing with nothing locally queued), instead of only when a local track is loaded. |
| 60 | |
| 61 | ### Changed |
| 62 | |
| 63 | - **Settings layout.** Storage locations and About moved to the bottom of the left column (two-column layout) so the device list in the right column can grow without burying them. |
| 64 | |
| 65 | ### Known limitations at 0.2.0 |
| 66 | |
| 67 | - **GNU/Linux only.** macOS / Windows are out of scope; the build pipeline produces `.deb` and `.AppImage` exclusively. |
| 68 | - **x86_64 only.** ARM64 builds are technically possible (libvlc supports it, Compose Multiplatform supports it) but not packaged today. |
| 69 | - **Observer volume on uncooperative devices.** If a followed device never volunteers its volume level, the slider stays at its prior value briefly and a fail-safe opens volume control after a short timeout; the first adjustment may jump. |
| 70 | - **No device-side library indexer.** Earhorn doesn't scan local files into a library — point it at a UPnP server. |
| 71 | - **Single-server browse.** Switching takes one tap; cross-server search/browse is not a feature. |
| 72 | |
| 73 | ## [0.1.0] — Initial release |
| 74 | |
| 75 | First public release of Earhorn Music for GNU/Linux. The desktop sibling to Earhorn for Android (Android TV / phone / Auto). Both products share a Kotlin Multiplatform `shared/` module — the same DidlItem domain, UPnP/MinimServer logic, ViewModels, and theme tokens render on Android and on the desktop. Only the renderer (libvlc via VLCJ vs Media3) and the screens (Compose Multiplatform vs Compose Android) differ. |
| 76 | |
| 77 | ### Added |
| 78 | |
| 79 | - **UPnP/DLNA discovery.** SSDP via jUPnP 3.0.3, with a local-IP picker that filters out Docker / KVM / libvirt / VPN virtual interfaces so discovery binds to the real LAN address. |
| 80 | - **ContentDirectory browse.** Faithful MinimServer `indexTags` rendering; synthetic entries (`>> Show All`, `[folder view]`, `[untagged]`) get distinct sage-green italic typography. |
| 81 | - **Local playback via libvlc / VLCJ.** FLAC, MP3, WAV, AAC, ALAC, DSD, Opus — anything libvlc handles. Gapless playback native. |
| 82 | - **Drag-to-reorder queue.** Real-time visual reordering during the drag; the currently-playing track keeps playing through the reorder. Uses `sh.calvin.reorderable` 2.4.3 with a frame-yield in the `onMove` callback so the LazyColumn's `layoutInfo` settles before the library reads it for the next drag tick (fixes the "tracks resort themselves" race). |
| 83 | - **Lyrics.** Time-synced lyrics from lrclib.net with server-side `.lrc` fallback. |
| 84 | - **Album notes.** `AlbumInfoLoader` reads ID3 COMMENT / Vorbis DESCRIPTION from the first track in an album, falling back to sibling `.txt`, `review.txt`, or `info.txt` files in the same UPnP directory. |
| 85 | - **Album art.** Coil 3 with a process-lifetime LRU cache (32 entries) backing the HeroArt component. Lightbox on click for full-screen viewing. |
| 86 | - **Random picker** (Albums / Tracks) and **Party mode** (42 random tracks, instant play, gramophone-button entry point). |
| 87 | - **M3U save/load** for queue persistence. |
| 88 | - **Sleep timer** with standard intervals. |
| 89 | - **Classical-listener metadata defaults.** Composer, Conductor, Work, Performer, Ensemble, sample rate, bit depth surfaced by default; 28 configurable fields total. |
| 90 | - **Keyboard shortcuts.** `Space` (play/pause, suppressed in text fields), `Ctrl+K` (search), `Esc` / `Alt+←` (back), plus menu accelerators for transport and navigation. *(Corrected: this entry used to list `Ctrl+F` and bare `←`/`→`, which were never bound.)* |
| 91 | - **Steampunk-ear dock icon.** Distinct from the in-app brand badge so the OS taskbar / Plasma panel doesn't render the same image twice. |
| 92 | - **`.deb` packaging** with a custom `enhanceDeb` Gradle finalizer: patches all `.desktop` files with `StartupWMClass`, `Categories`, `Keywords`, installs hicolor icons at 48/64/128/256/512, writes AppStream metainfo at `/usr/share/metainfo/dev.stave.earhorn.metainfo.xml`, rewrites `DEBIAN/control` Description, and writes `/usr/share/doc/earhorn/copyright` in Debian 1.0 format. |
| 93 | - **`.AppImage` packaging** via a custom `enhanceAppImage` task that post-processes the `jpackage` app-image directory into a real AppImage (writes `AppRun`, root-level `.desktop` and `.png`, hicolor icons, runs `appimagetool`). |
| 94 | - **About page** with the Faveat "Say thanks" ko-fi button (deep-espresso color so the yellow peace-emoji has clean contrast — same pattern as Trove). |
| 95 | - **Faveat Software** footer link to the brand site. |
| 96 | - **Comprehensive error swallowing.** Compose `WindowExceptionHandlerFactory` catches all Compose-side throws and logs to stderr without crashing; `CoroutineExceptionHandler` installed on every ViewModel `safeLaunch` does the same for coroutine throws. No more opaque "Error: index: N, size: N" dialogs interrupting playback. |
| 97 | |
| 98 | ### Known limitations at 0.1.0 |
| 99 | |
| 100 | - **GNU/Linux only.** macOS / Windows are out of scope; the build pipeline produces `.deb` and `.AppImage` exclusively. |
| 101 | - **x86_64 only.** ARM64 builds are technically possible (libvlc supports it, Compose Multiplatform supports it) but not packaged today. |
| 102 | - **No Cast sender.** The Android sibling has Google Cast (`play` flavor); the desktop does not. |
| 103 | - **No remote-renderer control yet.** The `Renderer` interface is designed for multiple implementations; 0.1.0 ships `VlcjLocalRenderer` only. Future `AVTransportRenderer` and `CastRenderer` would plug in behind the same interface. |
| 104 | - **No device-side library indexer.** Earhorn doesn't scan local files into a library — point it at a UPnP server. |
| 105 | - **Single-server browse.** Switching takes one tap; cross-server search/browse is not a feature. |
| 106 |


