# Release Notes ## v5.7.0 (2026-11-10) ### New features #### Android Auto The Android app now shows up in Android Auto. Its four tabs are Playlists (your playlists, Most Played, Liked, and the Recently added and Latest releases albums), Artists (one alphabetical list, with Android Auto's A-Z button to jump), Radio (your stations) and Podcasts (your subscriptions and their episodes). Tapping a song loads its album or playlist into the queue and starts at that song. The player screen has play, pause, next, previous and seek, a button that switches the playback mode (in order, shuffle, repeat song, repeat queue) and the upcoming queue. Search finds artists, albums and songs, and "play ... on RSPlayer" works by voice. The app does not have to be open on the phone: Android Auto starts the player by itself. Android Auto lists apps from outside the Play Store only when "Unknown sources" is turned on. On the phone open Android Auto's settings, tap "Version" ten times to turn on developer mode, then enable "Unknown sources" in the three-dot menu under Developer settings. Lists show no cover art yet. ### Improvements #### Playback mode button in the Android notification The media notification and the lock screen controls of the Android app have a new button that shows the playback mode and switches to the next one. #### Play works right after the Android app starts A press on play from a headset or from Android's media controls, before the app had loaded the current song, did nothing. It now continues what was playing last. ### Fixes #### Update library no longer empties the library when the music folder can't be read When a network share stopped answering but stayed mounted (for example after the NAS restarted with NFSv4 turned off), "Update library" found no files and removed every song. Now a music folder that can't be read, or that is empty (what an unmounted share looks like), keeps its songs, and the scan summary names it, for example "/mnt/rsplayer/music could not be reached". A subfolder that can't be read keeps only its own songs. A full rescan of an unreachable folder runs as a normal update instead of clearing the library first. Remount the share, then update the library again. #### Dead network shares show as "Not accessible" Settings showed such a share as mounted. It now shows "Not accessible", because RSPlayer checks that the folder can actually be listed instead of only that it is mounted. #### Library scan messages in the system log The default log settings of the Linux packages left out the library scanner and the other parts of the player, so scan results and warnings never reached the journal. Installations that never changed `/opt/rsplayer/env` pick up the new defaults when they update. ### Dependencies - **Rust 1.99.1** (was 1.98.1) for every build, including the Android app and its F-Droid build. Since this version, the web UI is built without stripping, because stripping now also removes data that the web UI build needs. - **tauri 2.12.5** with **tauri-plugin-window-state 2.5** (desktop and Android app), **iroh 1.4.1** and **iroh-mdns-address-lookup 0.6** (multiroom), **souvlaki 0.9** (desktop media keys), **rusty-s3 0.11** (remote S3 libraries), **tokio 1.54.1**, **rustls 0.23.46**, **wasm-bindgen 0.2.130** and smaller updates. - **feed-rs** (podcast feeds) stays at 2.4: version 3.0.0 drops the episode duration and artwork of many podcast feeds. - Web UI build tools: **Tailwind CSS 4.3.4** and **daisyUI 5.7.50**. ## v5.6.0 (2026-10-31) ### Improvements #### Remote access is a setting, off by default on Android The player has no login, so whoever can reach its web interface can control playback, change settings and download the music. The desktop and Android apps used to listen on the whole network. They now listen only on the device itself unless "Allow access from other devices" is turned on in Settings, Remote Access (takes effect after Restart RSPlayer). On Android it is off, including on existing installations, because a phone often joins networks it doesn't trust; the desktop app keeps it on, as before. The "Open on another device" address is shown only while the setting is on. Multiroom has its own connection and works either way. The headless server still listens on the network, and `BIND_ADDR` still overrides all of this. #### No photo permission on Android The app asked for access to photos and videos at first start. Covers come from the music files' own tags, so the permission was never used, and granting it gave access to every photo on the phone. The app now asks only for audio files and notifications. #### Android app for x86_64 A new `rsplayer__android_x86_64.apk` runs on emulators and x86 Chromebooks. The ARM app crashed at start there: under ARM translation it could not see the folder the Android side hands it. It now finds the folder on its own in that case as well. ### Fixes #### Cover art setting described correctly The hint under "Album art background and cover download" said that with the setting off, covers stored next to your music are shown. Only covers embedded in the music files are; the hint and the documentation now say so. #### Clearer messages Some messages in the app were reworded to read more plainly, among them the welcome dialog, the folder picker, the multiroom group notice and the restart confirmation. ## v5.5.2 (2026-10-19) ### Improvements #### Remote library updates survive network drops When the connection to a remote (S3) library dropped during an update, for example a phone switching networks or Wi-Fi going to sleep, every song read at that moment failed, and a few seconds offline could mark hundreds of songs as unreadable. Now the update notices the outage, shows "Connection lost, waiting for it to come back…" and waits up to 5 minutes, then continues where it stopped. A remote library that cannot be listed is named in the update summary instead of being silently skipped. #### Incomplete library updates are shown If an update did not finish (the app was closed, or the connection stayed down), Settings, Music Library shows a warning, and a warning icon appears on the section header even while it is collapsed. Press **Update library** again and only the missing songs are read. Files that cannot be read at all (for example a damaged file) are now named in the update summary, up to three of them, so you can find and fix them. The result of the last update is now kept across restarts. #### Android: library updates keep running in the background With nothing playing, Android froze the app soon after it left the screen, so a large library update stopped half way and the rest of the songs stayed missing. Updates now keep running with the screen off or another app open, with a notification showing their progress; it disappears when the update is done. #### The play button shows when a stream is buffering Starting a radio station, podcast or remote song can take a few seconds while it connects. The play button now shows a spinner until the music starts, instead of looking like nothing happened. Clicking it stops the stream. #### Bigger, easier to use mini player The player bar at the bottom of the Queue, Library and Settings pages is now taller and shows the album cover, with larger previous, play and next buttons that are easier to hit on a touchscreen. On wider screens the volume slider is much wider and gets a mute button and the volume in percent, and the elapsed and total time of the song are shown under the title. ### Fixes #### Stop and Next respond while a stream is connecting Pressing pause, stop or next while a radio, podcast or remote song was still connecting did nothing until the connection succeeded or failed, and meanwhile the player ignored all other commands. A stream that stopped sending data could even freeze the player completely. These now respond at once. #### Streams that stop sending data reconnect A stream whose connection stays open but stops delivering audio is now treated as dropped after 20 seconds and reconnected, instead of staying silent forever. ## v5.5.1 (2026-10-14) ### Improvements #### Faster library scans New songs are now read several at a time, and songs on remote storage are read with small requests that fetch only the tags and artwork instead of streaming the whole file. Over the internet this made reading a song about 15 times faster (from 2 to 5 seconds to about 0.15 seconds each). If you serve a NAS folder with `rclone serve s3`, also add `--buffer-size 0` (see [Remote Library](remote_library.md)): rclone otherwise reads 16 MB ahead for every file, which slowed scans down about 4 times on a busy disk. ### Fixes #### Settings no longer lost when switching theme or visualizer Switching the visualizer (button or V key), the theme or the album art background, or closing the welcome dialog saved a copy of all settings taken when the app was opened. Anything added in the settings since then, such as a music folder or a remote library, was quietly removed again. These actions now save only the display preferences. This showed most in the Android app, which stays open for a long time. #### Remote library URLs copied from a browser A remote library URL with escaped characters, such as `%20` for spaces in a folder name (as browsers show and copy them), found no files. Such URLs now work as typed. ## v5.5.0 (2026-10-13) ### New features #### Remote music libraries (S3) A music library no longer has to be a folder on the player's machine. In Settings, Music Library, Remote Libraries (S3), add any S3-compatible storage by its URL (for example `https://host/bucket` or `s3://bucket`) and access key: Amazon S3, Backblaze B2, Cloudflare R2, MinIO, Garage, or a NAS folder published with `rclone serve s3`. Its songs are scanned and played over the network without mounting anything, which makes a home NAS library playable on the Android app from anywhere. See [Remote Library](remote_library.md) for a NAS setup reached through a VPS. ### Improvements #### Library scan progress you can see A running library scan now shows under the navigation bar on every page: a spinner, what it is doing (looking for new music, reading new songs or removing missing ones), a progress bar with counts such as "Reading new songs 1,234 / 15,600", and the file being read. The same progress appears in Settings, Music Library, where the update buttons are disabled until the scan is done. When it finishes, a message sums it up, for example "Library updated: 12 new, 3 removed (2 min 10 s)", and stays in the settings as the last update. A page opened during a scan shows its progress right away. ### Fixes #### Desktop app quits on `kill` and Ctrl-C Stopping RSPlayer Desktop with `pkill`, `kill` or Ctrl-C in a terminal stopped the built-in server but left the window and tray icon running, so the app had to be killed with `kill -9`. The whole app now quits. #### Clearer error when mounting from a remote session On Linux, the desktop app can mount network shares only when it runs in the computer's own desktop session. Started over SSH or remote desktop, the mount failed with "not authorized by system policy"; the message now says that the app has to be started from the local desktop. ## v5.4.1 (2026-10-05) ### Improvements #### Online lookups are opt-in on Android For songs without their own cover, the player looks the cover up on Last.fm. The "Album art background" setting in Settings, Appearance now controls this download too and is renamed "Album art background and cover download": when it is off, nothing is sent to Last.fm and only covers embedded in or next to your music are shown. On a new Android installation the setting starts off; other platforms and existing installations keep their current setting. The podcast search directory (Settings, Podcasts) has a new choice, None: search is off and podcasts are added by pasting their feed URL. It is the default on a new Android installation; other platforms keep Apple Podcasts. #### Build without downloaded tools The Android app is now built without any tools downloaded during the build, as F-Droid requires: the web UI's stylesheet and icon font are part of the source code, and the WebAssembly tools come from the build system's own packages. ## v5.4.0 (2026-10-01) ### Improvements #### Visualizer data only goes where it is shown With visualization enabled, the server sent about ten level updates per second to every connected browser, phone and desktop window, even when none of them showed a visualizer. Each client now receives them only while it shows one: the player page is open, the visualizer is not set to None, and the tab or window is visible. This saves network traffic and battery on phones and tablets, and CPU on the server. The front panel's level meter is not affected. #### Desktop app remembers its window The desktop app now opens with the size, position and maximized state it had when it was last closed or hidden to the tray. On Linux with Wayland, only the size and maximized state are restored: Wayland lets the desktop, not the app, decide where windows open. ### Fixes #### Garbled track info on some radio stations Some radio stations send broken track info. The 101.ru streams, for example, send a leftover server reply (`{"status":1,"message":"Ok",...}` followed by stray line breaks) instead of the artist and song name, and the player showed it as the song title. Only the first line of the station's track info is used now, and values that look like such a reply are ignored, as are empty placeholders like ` - `. A title with an empty artist (` - Song`) now shows just the song. The player keeps showing the last good title or the station info instead. #### Cover art for radio songs For a radio station without its own logo, the player looked up the cover on Last.fm using the station description as the album name (for example Radio Paradise's "DJ-mixed blend of modern and classic rock..."), which never finds anything. Radio songs are now looked up by artist and song title, so the cover of the album the song comes from is shown. Stations whose metadata service reports an empty cover (FluxFM and other QuantumCast stations) no longer block the lookup, which also left an empty background. #### Current song on FluxFM and other QuantumCast and Radiosphere stations These stations publish the playing song through their own metadata service, while the stream itself often only names the channel ("FluxFM - Livestream"). The player read the service once when the station started, showed artist and title merged into one line, and a moment later replaced them with the channel name from the stream. The service is now asked again every few seconds while the station plays, so every song change is shown with a separate artist and title, and with the song's cover when the service has one. #### Track info and cover art on iHeartRadio stations iHeartRadio stations send the artist followed by a list of internal fields (`.38 SPECIAL - text="Hold On Loosely" song_spot="M" length="00:04:36" ...`), and the player showed the whole list as the song title. The artist and title are now read from it correctly, and the cover art the station sends along is shown instead of the station logo. ## v5.3.1 (2026-09-29) ### Fixes #### Desktop app froze when reopened from the system tray After the window had been hidden in the tray for a while, it opened frozen: the buttons, the right-click menu, the elapsed time and the visualization did not react, sometimes for a long time. While the window was hidden, every visualizer update (20 per second) was still drawn, and the Linux webview (WebKitGTK) kept all these drawings queued because a hidden window never shows them. After 20 minutes the queue took almost 2 GB of memory, and showing the window meant working through all of it first. The visualization is now not drawn while the window or browser tab is hidden. The memory stays flat, and the window reacts immediately when it is shown again. #### White flash when the page loads While the web interface was loading, the page was briefly white before the theme was applied, which was very visible with a dark theme. The page now shows the background of the last used theme from the first moment, and the interface appears only once its styles have loaded. ## v5.3.0 (2026-09-28) A new analog VU meter visualizer, visualizations that follow the volume, and a few privacy improvements in the web interface. Nothing needs to be migrated. ### New #### Analog VU meter The new **Analog** visualizer style shows two needle VU meters, one per channel, modeled on the meter on [rsplayer.de](https://rsplayer.de). - The meters sit above the track info at the full width of the player instead of behind it. They use the free space above the track info and controls, so on a large screen turning them on does not move the controls. - They follow the selected theme: the needle takes the theme's primary color and the zone above 0 its error color. - The scale runs from -20 to +3. 0 VU is a peak level of -3 dBFS, so only the loudest peaks reach the red zone. Like a real VU meter, the needles rise quickly and fall back more slowly. Turn on **Settings → Enable visualization**, then press **V** or the visualizer button until the meters appear. #### Arch Linux packages On Arch Linux and Manjaro, RSPlayer is now a real pacman package instead of a tarball extracted to `/`. The server is `rsplayer-bin` and the desktop app is `rsplayer-desktop-bin`. - `install.sh` and `install_desktop.sh` install them with `pacman -U`, so pacman resolves the dependencies, knows every installed file and can remove them again with `sudo pacman -R rsplayer-bin`. - An existing tarball install is taken over when you run the install script again. A changed `/opt/rsplayer/env` is kept, and the new default is saved next to it as `env.pacnew`. - The systemd unit and the polkit rule of the server moved to `/usr/lib/systemd/system` and `/usr/share/polkit-1/rules.d`, where Arch packages keep them. The script removes the old copies from `/etc`. ### Improvements #### Visualizations follow the volume Visualizations used to follow the volume only with software volume control. With an ALSA or PipeWire mixer they showed the level of the music whatever the volume was. Now every style follows the volume with every volume control type. - The display drops by half as many dB as the sound: at 50 % volume it is 9 dB lower instead of 18 dB. This keeps the needles and bars moving at normal listening volume, while turning the volume down still clearly lowers them. - With an ALSA hardware mixer the effect is approximate, because the mixer's own volume curve is not known and the software volume curve is used instead. - With volume control set to **Off**, visualizations show the level of the music itself, as before. #### Privacy - The web interface no longer sends the address or host name of your RSPlayer server as a referrer when it loads album covers, lyrics, the radio directory or the update check. - Album covers are always looked up on Last.fm over HTTPS, also when the web interface is opened over plain HTTP. - The update check asks GitHub at most once a day per browser instead of on every page load. - The [privacy policy](privacy_policy.md) now lists every third-party service the web interface contacts (Radio Browser, Last.fm, LRCLIB and GitHub). ### Fixes - The desktop install script could break the app on Arch Linux and Manjaro. It installed the dependencies with `pacman -S --needed`, which also upgrades an already installed but outdated WebKitGTK on its own, without the rest of the system. The app then failed to start with an error such as `libjxl.so.0.12: cannot open shared object file`. The script now installs the app as a pacman package, which only installs dependencies that are missing. If you hit this error, run `sudo pacman -Syu`. - Visualizations drawn behind the player could look stretched on phones, and after resizing the window or rotating the phone, because their drawing area kept the size it had when the player first opened. It now follows the size of the player area. ## v5.2.0 (2026-09-26) ### New #### Radio and podcasts play without using the queue Playing a radio station or a podcast episode used to add it to the end of the queue and jump there. The queue filled up with stations, and Next/Previous jumped out of the stream back into your music. - Stations and episodes now play directly; the queue and your place in it stay as they were. The station or episode playing is highlighted on the Radio and Podcasts pages. - **Radio:** Next/Previous switch between your favorite stations and the ones you added by hand, from the player, the desktop tray, media keys, the Android notification or a hardware panel. Now Playing shows a **Live radio** badge and hides the seek controls; the heart adds or removes the station from Favorites. - **Podcasts:** when an episode ends, the next newer unplayed episode of the same show plays. Next/Previous move to the newer/older episode. - **Back to queue** on Now Playing and on the Queue page continues the queue song where you left it. Playing anything from the queue or the library switches back to the queue too. - **Add to queue** / **Play next** are still available for stations and episodes you want in the queue. - Favorites starred before this version are picked up the first time the Radio page is opened. ### Fixes #### Desktop app on Linux: sliders did not react to touch On touchscreens (e.g. a Surface running Ubuntu), dragging the volume or seek slider with a finger moved it but changed nothing; only the + and − buttons worked. The Linux desktop webview (WebKitGTK) does not report the end of a touch drag the way browsers do, so the new position was never sent. The sliders now send it when the finger is lifted. #### Faster startup: the database no longer replays old journal data Every start read the database's write-ahead journal from the beginning, and the journal is only split into a new file once it passes 64 MB, so it kept growing between restarts. On a long-running Raspberry Pi 4 opening the database took 1.4 s, and on a development machine with a 42 MB journal 8.8 s. RSPlayer now closes the journal and starts a fresh one right after writing everything to disk at startup and shutdown, and the old journal is deleted. Opening the database now takes about 0.02 s on the same Pi 4. This needs a small addition to the database library, fjall, which RSPlayer now builds from its own fork until the change is available upstream. #### Raspberry Pi Zero / Pi 1 (ARMv6) builds crashed on start The ARMv6 server binaries and packages (`armhfv6`) were built with a compiler setup that targets ARMv7, so they crashed immediately on a Raspberry Pi Zero, Zero W or Pi 1 without printing anything. This affected every release since at least 4.0.0. The ARMv6 builds now use a proper ARMv6 toolchain and start normally on a Pi Zero W. ### Changes - The `RSPLAYER_SKIP_DB_FLUSH` and `RSPLAYER_RESET_BLOATED_KEYSPACES` switches added in 5.1.0 are gone. They were a precaution for a startup crash on one Raspberry Pi 3 ([#36](https://github.com/ljufa/rsplayer/issues/36)); the user's database turned out to be fine, the crash could not be reproduced in 48 starts on x86 and Raspberry Pi hardware, and it has not happened again on that Pi since. The one-time rebuild of the playback-state table from 4.9.5 runs again on the first start of databases that have not had it yet. ### Dependencies - **fjall 3.1.10** (database), still with RSPlayer's journal-rotation patch: fixes a race between compaction and clearing a table (used when the album list or the random-play history is reset) and the handling of journal files recovered after a crash. - **iroh 1.2** (multiroom), **rustls 0.23.45**, **tauri 2.11.6**, **tower-http 0.7.1**, **ureq 3.4.2**, **sha1 0.11**, **dirs 7** and smaller updates. Podcast and episode ids are unchanged. ## v5.1.1 (2026-09-24) A Snap-only release: the other packages stay at 5.1.0. ### Fixes #### Snap: choppy playback on PipeWire systems On Ubuntu 24.04 and later (and other systems that use PipeWire, such as Zorin OS 18), the Snap played through the PulseAudio compatibility layer and hit constant buffer underruns: playback started late and stuttered, and the log filled with `audio output error (n/30): A buffer underrun or overrun occurred`. - The Snap now asks the system's sound server at startup. On PipeWire it plays directly through PipeWire's own ALSA plugin, the same way the Flatpak does. On PulseAudio systems such as Ubuntu 22.04 it keeps using PulseAudio. - The Snap no longer reads the system's own ALSA configuration (`/etc/alsa/conf.d`, `/etc/asound.conf`). This also fixes the "Default" output failing on Fedora and other non-Ubuntu systems. - Direct `hw:` output to a USB DAC (after `sudo snap connect rsplayer:alsa`) is unchanged. ### Build and CI - The "Full release" workflow can publish the Snap alone: run it manually with target `snap` and **publish_snap** enabled. A tag push still runs the full release. ## v5.1.0 (2026-09-24) The desktop app can now keep playing from the system tray when its window is closed, and on Linux it uses a compact title bar. The player has 10-second skip buttons, internet radio recovers from short dropouts on its own, and the web UI stays in step with the player after a slow or interrupted connection. Nothing needs to be migrated. ### Features #### Desktop app: system tray and minimize on close The desktop app (Linux, Windows, macOS) has a system tray icon with **Show RSPlayer**, **Previous**, **Play/Pause**, **Next** and **Quit**. A new **Settings → Desktop App** section holds the window options. - **Minimize to tray on close** (on by default): closing the window hides RSPlayer to the tray and playback continues. Quit from the tray menu. Turn it off to make closing the window quit the app. - Launching RSPlayer again while it is hidden brings the running window back instead of starting a second copy. - **Linux:** the tray icon needs `libayatana-appindicator3`. The deb and rpm packages recommend it and the Arch package lists it as an optional dependency. Without it there is no tray icon and closing the window quits, whatever the setting. Stock GNOME shows no tray icons unless the *AppIndicator and KStatusNotifierItem Support* extension is installed, so either install the extension or turn the setting off. The Flatpak and Snap apps have no tray icon yet. #### Desktop app: compact title bar on Linux On Linux the desktop's title bar is replaced by RSPlayer's navigation bar, which saves a row of screen space (useful on small touch panels). Drag its empty space to move the window, double-click it to maximize, and use the minimize, maximize and close buttons on its right. **Settings → Desktop App → Compact title bar** turns it off and brings the desktop's own title bar back; the change applies right away. Windows and macOS keep their normal title bar. #### Skip 10 seconds back and forward The player controls have **Back 10 seconds** and **Forward 10 seconds** buttons next to Play/Pause, the same as the existing Shift+Left and Shift+Right keyboard shortcuts. They also work in browser playback mode, and are disabled for live radio, which cannot seek. ### Improvements #### Network streams recover from dropouts - An internet radio station or podcast whose connection drops is reconnected up to 3 times (after 1, 2 and 4 seconds), with a single "Stream interrupted, reconnecting..." message per outage instead of one per attempt. A podcast continues from where it stopped. A stream that played for at least 30 seconds before dropping gets a fresh set of retries, so a long radio session survives several short outages. - Local files and audio output errors are no longer retried, since they fail the same way again. A song that cannot be played is skipped with a notification and the queue continues. - Playback stops with an error, instead of running through the whole queue, when the audio output is missing, busy or rejects the format, or when 3 songs in a row fail (for example a library share that went offline, or Loop Single on a broken file). #### The web UI stays in step with the player - A client that falls behind (a phone browser in the background, slow Wi-Fi) is sent the current song and player state again, so it no longer shows a stopped player as playing or an old song. The server also buffers more updates per client before it drops any. - Reopening the player, or reconnecting, shows the live title of the radio station being played, not only the station entry from the queue. Liking or disliking the playing song updates its indicator immediately. - When the queue plays to the end, the player shows as stopped, and pressing Play starts again from the beginning of the last song instead of trying to resume a finished one. ### Fixes - Turning DSP on or off in Settings is saved right away and asks for the restart it needs, like the other playback settings. Before, the change was only kept after a separate save. - **Raspberry Pi startup crash ([#36](https://github.com/ljufa/rsplayer/issues/36)):** on an affected database the server aborted at startup while writing its cached data to disk. Two switches work around it without losing data: `RSPLAYER_SKIP_DB_FLUSH=1` skips that write at startup and shutdown (everything is still saved in the database journal), and the one-time rebuild of the playback-state table added in 4.9.5 now runs only with `RSPLAYER_RESET_BLOATED_KEYSPACES=1`. The root cause is still being investigated. ### Build and CI - Release builds of the Snap are now published to the Snap Store **beta** channel instead of candidate (`sudo snap install rsplayer --channel=beta`), and promoted to stable after testing. ## v5.0.0 — 2026-09-22 Internet radio stations can now be saved by hand and kept in Favorites for good, and the album rows in the library can be scrolled sideways with a mouse. Nothing needs to be migrated: the library, queue, playlists, statistics and settings are read exactly as before. ### Features #### Save your own radio stations Library → Radio → Favorites has an **Add station** button. Enter a station name, the stream URL and, if you have one, a logo URL, and the station is stored by the server. It stays in Favorites after the queue is cleared, the browser is closed or the machine restarts. Until now a stream that is not listed in the radio-browser directory could only be dropped into the queue, and was gone with the next **Clear**. - Saved stations are listed above the starred radio-browser stations, sorted by name, each with **Add to queue**, **Play now**, **Edit** and **Remove** (removing asks for confirmation). - They are also shown when the radio-browser directory cannot be reached, so a self-hosted or local stream keeps working without an internet connection. - The same stream URL cannot be stored twice; the message names the station that already uses it. - Stations live on the server, not in the browser: one added on a phone appears on every other client right away, and the list is part of the server database. #### Sideways scrolling for the cover rows Recently Added, New Releases, Saved Playlists, Favorites and the genre and decade rows scrolled only by touch or by holding Shift while turning the mouse wheel, because the rows have no visible scrollbar. They now have arrow buttons on the left and right, each shown only while there is something to scroll in that direction, so covers past the right edge can be reached with a mouse. Swiping on a phone or tablet works as before. ### Documentation - New **[Privacy Policy](https://docs.rsplayer.de/#/privacy_policy)** and **[Disclaimer](https://docs.rsplayer.de/#/disclaimer)** pages, linked from the README, the documentation home page and the installation guide. - The [usage guide](https://docs.rsplayer.de/#/usage?id=radio-view) describes the Radio page in full: the Favorites tab, adding and editing your own stations, and browsing the directory by search, country, language or tag. ### Build and CI - The Android release is built inside a pinned Docker image (`docker/Dockerfile.android`), built and published by its own workflow, instead of installing the toolchain on the runner for every build. Two builds of the same version stay byte-identical, which is what the F-Droid submission checks. ## v4.9.9 — 2026-09-21 Android app fixes, a new Android application ID, and smaller, reproducible Android builds for the F-Droid release. ### Changes #### Android - **New application ID `de.rsplayer.app`** (was `io.github.ljufa.rsplayer`). The change applies to the Android app only; the desktop apps, Flatpak and Snap keep their IDs. Android treats this as a different app, so it installs next to an older APK and does not update it: uninstall the old one to avoid two copies. - The Android app now reports its real version (4.9.9) and a proper version code. Earlier APKs were all built as version 1, so app stores could not tell newer builds from older ones. - The update banner that checks GitHub for new releases is no longer shown in the Android app, because updates come from the store the app was installed from. - The app no longer registers an Android TV launcher entry. It is a phone and tablet app. - The Android release file is now named `rsplayer__android.apk` (it was `rsplayer-desktop__android.apk`). - The release APK no longer contains an x86_64 build, so it is about a third smaller (ARM64 and ARMv7 only). x86_64 is only useful on emulators and some Chromebooks; build from source to get it (see the build guide). - The F-Droid build is split per CPU architecture (armeabi-v7a and arm64-v8a), so each download is between a quarter and a third of the size of the universal APK (about 23 MB for armv7 and 31 MB for arm64, instead of 81 MB). The version code of each APK ends in a digit for its architecture (1 or 2), for example 40090092 for arm64. - **Reproducible builds.** The Android release workflow now builds the web UI itself with pinned tool versions and publishes one signed APK per CPU architecture (`rsplayer__android_arm64-v8a.apk` and `rsplayer__android_armeabi-v7a.apk`) next to the universal one. F-Droid builds the app from source and checks that its result matches these files, so its copy carries our signature instead of its own. Two independent builds of the same version now produce byte-identical APKs. - The web UI's stylesheet is now loaded as `/tw.css?v=`, so the build no longer depends on where it runs; the version in the address still refreshes the browser cache after an update. ### Fixes - Fixed a crash when the Android app is opened while the screen is locked or dozing. Android refuses to start the playback service from the background, and the app now retries the next time it is on screen. ## v4.9.5 — 2026-09-18 A fix for slow startup on Raspberry Pi and other small devices, a layout fix for the Artists page on small screens, and updates to the audio libraries. ### Fixes #### Slow startup on Raspberry Pi On a long-running Raspberry Pi 4 the server could take 15–25 seconds between "Starting RSPlayer" and "Shared database opened", so the web UI stayed unreachable after every restart. The database keeps a write-ahead journal that is replayed on each start. Playback progress is saved about once a second, and the database only trims that journal once a table grows to 64 MiB, which took months. Over time the journal grew to about 67 MB and roughly 650,000 entries. - The database now opens in under a second. On a Raspberry Pi 4 that had the problem, the database directory dropped from 80 MB to 15 MB. - The database is flushed to disk at startup and on a graceful shutdown, so the journal stays small even after a restart from the UI, a crash or a power cut. - The busiest small tables (playback state, queue, queue status, play statistics and settings) now use a 2 MiB flush threshold on new installs. - **One-time reset:** the first start after upgrading recreates the playback-state table. The last played position and paused state are forgotten once, so the first resume after the upgrade starts from the beginning. Queue, playlists, library, statistics and settings are not touched. #### Artists page on 800×480 screens On short displays such as the 800×480 Raspberry Pi touch panel, the A–Z jump rail on the Artists page was squeezed into a band of about 240 px, so its 27 letters were hard to read and tap. On viewports up to 560 px tall the rail now stretches between the top bar and the footer player and uses the rest of the screen. ### Dependencies - **cpal 0.18.2** (audio output), merged into the RSPlayer fork, which keeps its ALSA fix that recovers from EIO errors on plugin devices such as the PulseAudio route inside the Flatpak sandbox. Upstream's new suspend/resume check replaces the fork's older one. - **Symphonia 0.6.1** (decoding), merged into the RSPlayer fork, which keeps its fix for an infinite loop in radio streams. - **rubato 5** (sample-rate conversion) and **iroh-mdns-address-lookup 0.5** (multiroom discovery). ## v4.9.0 — 2026-09-15 RSPlayer runs on Android. The desktop app — the full player with the server inside — now ships as an Android APK, so a phone or tablet plays music from its own storage, offline, through the device's audio output. ### Features #### Android app (beta) `rsplayer-desktop__android.apk` is attached to the release (Android 8.0+, ARM64/ARMv7/x86_64 in one file; sideload it — a Google Play listing is planned). It is the same app as on the desktop, wrapped for Android: - **Local music**: on first start the app asks for the media permission and scans the shared **Music** folder. More folders can be added in Settings. Podcasts and internet radio work as before. - **Background playback**: playback continues with the screen off or another app in front. Play/pause, next/previous and seek are available from the lock screen, the notification and headset/Bluetooth buttons; the app pauses for calls and when headphones are unplugged. - **Audio output** through AAudio to the current system output (speaker, wired, Bluetooth), with software volume. - **Multiroom** discovery works on Wi-Fi (beta), like on the desktop. - Not available on Android: network mounts (SMB/NFS), ALSA device selection, DSD output, and `.cue`/`.m3u`/`.lrc` sidecar files (Android hides non-media files from apps). #### Folder picker for the music library Settings → Music Library has a **Browse folders…** button, so adding a library no longer means typing a path. It opens a folder browser for the machine RSPlayer runs on, starting from your Music folder, removable drives and mounted shares (on Android: the shared Music folder, internal storage and SD cards), and shows how many sub-folders and audio files each folder holds. It works the same in a browser, the desktop app and the Android app. The desktop app also adds your Music folder to the library on first start. Folder browsing is disabled on the public demo. ### Build and CI - `crates/desktop` is now a library with a thin binary, as Tauri mobile requires; the Android Gradle project lives in `crates/desktop/gen/android` and is built with `cargo make build_android_dev` / `build_android_release` (see the [build guide](https://ljufa.github.io/rsplayer/#/build?id=android-desktop-app)). - The server's process-global setup is idempotent so the Android app can restart the backend in-process. - The "Full release" workflow builds the Android APK on a self-hosted runner labelled `android` and attaches it to the release. ## v4.8.5 — 2026-09-14 The desktop app now runs on ARM64 Linux, including the Raspberry Pi 4 and 5. This release also simplifies the install instructions and makes the installer tell you where to open RSPlayer. There are no changes to playback or the web UI. ### Features #### Desktop app for ARM64 Linux (Raspberry Pi 4/5) The desktop app was previously x86_64-only on Linux. Every release now also ships it for ARM64, in all the same formats: - **Native packages:** `rsplayer-desktop__arm64.deb`, `rsplayer-desktop--1.aarch64.rpm` and an Arch tarball. They are published to the [package repository](https://ljufa.github.io/rsplayer-pkg) too, so `install_desktop.sh` and `sudo apt install rsplayer-desktop` work on a Raspberry Pi. The packages need glibc 2.34 or newer, so they install on Raspberry Pi OS bookworm and trixie. - **Snap:** `sudo snap install rsplayer` now installs on ARM64. - **Flatpak:** the [RSPlayer flatpak repo](https://ljufa.github.io/rsplayer-flatpak) carries both architectures, and the same install command picks the right one. A separate `rsplayer-desktop__arm64.flatpak` bundle is attached to the release. As on x86_64, the desktop app has no background service — it runs when you open it. You need a graphical desktop session (for example Raspberry Pi OS with desktop). #### Installer prints the web UI address When `install.sh` finishes, it now shows the address to open in your browser (for example `http://192.168.1.20`), using the machine's LAN address and the port from `/opt/rsplayer/env`. If the service didn't start, it prints the command to start it first. ### Documentation - **Simpler install instructions.** The README and the [installation guide](https://ljufa.github.io/rsplayer/#/installation) start with a "Which one do I need?" table — server or desktop app, per platform — followed by one recommended command for each case. Manual repository setup, package files and the release file-name tables moved further down. - **Podcasts added to the feature lists** in the README, the documentation home page and the [feature comparison](https://ljufa.github.io/rsplayer/#/feature_parity), which gains a Podcasts row. - The documentation home page links to the new online demo address, , and its comparison table now lists multiroom as available (beta) instead of planned. ### Packaging - Snap and Flatpak store descriptions mention podcasts, and the desktop entries gain `radio` and `podcast` search keywords, so the app shows up when you search for them in your application menu. - Fixed the generated Arch desktop `PKGBUILD` declaring `arch=('amd64')`, which `makepkg` rejects. It now declares `x86_64` or `aarch64`. - **Fixed the desktop install script on Arch Linux.** `install_desktop.sh` asked pacman for `libappindicator-gtk3`, which is no longer in Arch's official repositories (nor in Arch Linux ARM), so the install stopped with "target not found" before installing anything. The app doesn't use it, so it was removed from the script and from the Arch `PKGBUILD` dependencies. ### Build and CI - **Single-build test runs.** Every option of the manual `cd.yml` run now starts exactly one build: one server target, `desktop-x86_64`/`desktop-aarch64`, `snap-amd64`/`snap-arm64`, `flatpak-amd64`/`flatpak-arm64`, `macos-aarch64`/`macos-x86_64` or `windows`. `all` and tag pushes still build everything. - The ARM64 desktop packages are cross-built with the existing `rsplayer-cross-aarch64` image, which now includes the arm64 WebKitGTK development libraries. The ARM64 Snap and Flatpak build on GitHub's native arm64 runners. - Server and desktop cross builds now pull the latest cross image first. Before, a self-hosted runner kept using a stale cached image after the image was rebuilt. - The Snap now compiles and links against Ubuntu's own libraries instead of mixing them with the GNOME SDK snap's, which failed to link on ARM64. - Publishing to the flatpak repo imports every architecture's bundle. **Full Changelog**: https://github.com/ljufa/rsplayer/compare/4.8.0...4.8.5 ## v4.8.0 — 2026-09-11 Podcast support, seekable HTTP audio, and an A–Z jump index for the artists list. ### Features #### Podcasts RSPlayer is now a podcast client. A new **Library → Podcasts** tab (keyboard shortcut `O`) lets you: - **Find shows** by name through the Apple Podcasts directory (no account needed), or through [Podcast Index](https://podcastindex.org) when you enter a free API key in Settings → Podcasts. Pasting an RSS feed URL into the same search box subscribes directly. - **Subscribe** and browse each show's episodes newest-first, with artwork, publish date, duration, and the show notes one tap away. Feeds are refreshed in the background (hourly by default, conditional requests so unchanged feeds cost one small round trip) and a refresh button is there when you can't wait. - **Play, play next, or add to queue** any episode. Episodes enter the regular queue with proper title/show/artwork metadata, so the Now Playing page, the footer player, multiroom and the queue all just work. - **Seek and resume.** Episodes are seekable like local files — the progress bar shows the real length and dragging it works. RSPlayer remembers where you stopped in every episode (across restarts and whichever way you start it again), shows a progress bar and "12m left" in the episode list, and marks an episode played once you have heard 95 % of it (threshold configurable). Played episodes are dimmed and can be toggled back to unplayed; an unplayed-count badge sits on each show's card. Settings → Podcasts holds the directory choice, Podcast Index credentials, refresh interval, how many episodes to keep per show, and the played threshold. Resume positions are tracked when RSPlayer plays the audio itself; the optional browser-playback mode does not report positions yet. #### Seeking works for direct HTTP audio URLs Any `http(s)://` URL added through the queue's "Add URL" dialog that points at a file on a host supporting byte ranges is now seekable and shows its real duration. Live radio streams behave as before. As part of this the HTTP stream reader also reconnects at the current byte offset if the connection drops mid-track instead of restarting from the beginning, and it no longer carries a global timeout that also bounded body reads. #### A–Z jump index in Library → Artists ([#34](https://github.com/ljufa/rsplayer/issues/34)) Large libraries needed a lot of scrolling to reach artists late in the alphabet. The artists list now has a letter rail along its right edge: tap a letter to jump to the first artist under it, or press and slide along the rail to scrub through the list with a large letter indicator, the way phone contact lists work. Letters with no artists are dimmed, `#` collects names starting with digits or symbols, and accented names are bucketed the same way the server sorts them (Émilie under E). The rail appears once the list has 20 or more artists, including filtered search results. ### Fixes - **Non-ICY HTTP URLs no longer lose their queue metadata.** A direct file URL used to be treated like a radio stream: the (empty) station metadata replaced the queued title and the reported sample rate/bit depth came from absent ICY headers. Only responses that actually carry `icy-*` headers are treated as radio now. **Full Changelog**: https://github.com/ljufa/rsplayer/compare/4.7.5...4.8.0 ## v4.7.5 — 2026-08-30 Maintenance release: dependency updates plus a fix for a break one of those updates introduced. ### Fixes - **Resampler broke after the `rubato` 4.0 bump.** The dependency update below pulled in `rubato` 4.0, which collapsed the `sub_chunks` parameter out of `Fft::new()`. Switched to `Fft::new_custom()` with the same `sub_chunks=2` and `BlackmanHarris2` window used before, so resampled playback behaves exactly as it did on 4.7.0. ### Dependencies - Bumped all Rust dependencies to their latest semver-compatible versions (`cargo update`), including `quinn-proto` 0.11.14 → 0.11.16, `serde_with` 3.20.0 → 3.21.0, and 15 more via Dependabot's grouped update. - Bumped web UI frontend dependencies (`tailwindcss`, `daisyui`) to latest; Now Playing and Settings pages verified in a real browser after the rebuild. - Bumped `actions/cache` from 4 to 6 in CI workflows. ### Documentation - Added a troubleshooting entry for **"Audio won't play while another player (e.g. Squeezelite) is running"** — covers the ALSA exclusive-device conflict, how to diagnose it via `journalctl`, and three fixes (free the device, share it via `dmix`, or use the other player's idle timeout). **Full Changelog**: https://github.com/ljufa/rsplayer/compare/4.7.0...4.7.5 ## v4.7.0 — 2026-07-14 ### Features #### Update Available Notifications RSPlayer now tells you when a new version is out. On app load the web UI checks the GitHub releases page (directly from the browser, once per load) and, if a newer version exists: - A **notification banner** appears with the new version number and a link to the release page. Dismissing it hides the banner for that version — it returns only when the next release ships. - The banner and the Settings page footer show the **exact update command for your install**: the server detects how it was installed (apt/deb, dnf/rpm, Flatpak, Snap, desktop app) and the UI offers the matching one-liner — e.g. `sudo dnf update rsplayer` or `flatpak update io.github.ljufa.rsplayer` — with a copy button. Snap users are reminded that snaps update automatically. - The Settings page now always shows the **running version**, with an "update available" badge when applicable. This closes the "In-app upgrade detection" and (together with the new package repositories below) the "Automatic Linux Updates" roadmap items. #### Network Mounts from the Desktop App (deb/rpm/arch) ([#27](https://github.com/ljufa/rsplayer/issues/27)) Adding SMB/NFS network mounts from the **desktop app** previously failed with *"Mount failed: Failed to create mount point: Permission denied"* — the desktop app runs as an unprivileged user-session process, and mounting a filesystem on Linux requires privileges that no user or group membership can grant (the headless server gets them from its systemd unit). The desktop deb, rpm, and arch packages now ship a small privileged helper (`/usr/libexec/rsplayer-mount-helper`) that the app invokes through **pkexec/polkit** when mounting or unmounting: - **No password prompts**: the bundled polkit policy authorizes users with an active local session automatically — the same model udisks uses for removable media. Mount, unmount, and the automatic re-mount of saved shares at app startup all just work. - **Narrow by design**: the helper is the only privileged part and it only mounts/unmounts under `/mnt/rsplayer/`, only accepts the cifs/nfs filesystem types, validates share names and mount options against a whitelist, and receives credentials via stdin so they never appear in the process list. - Works for both **SMB and NFS**, including shares previously saved from the "Detected Network Mounts" list. - **Unchanged elsewhere**: the headless server keeps its existing direct mount path, and the sandboxed Flatpak/Snap desktop builds still cannot manage mounts — mount via the system (e.g. `/etc/fstab`) there and the share appears under Detected Network Mounts. #### Desktop App — "Restart RSPlayer" Actually Restarts ([#26](https://github.com/ljufa/rsplayer/issues/26)) **Settings → Restart RSPlayer** now relaunches the desktop app: the embedded backend shuts down gracefully first (database persisted, port released), then the application starts itself again. Previously the app just closed and had to be started again by hand (on the headless server nothing changes — it exits and systemd respawns it, as before). ### Installation & Updates #### apt and dnf Package Repositories RSPlayer's Linux packages are now served from a self-hosted, GPG-signed package repository at [ljufa.github.io/rsplayer-pkg](https://ljufa.github.io/rsplayer-pkg) — an apt repo for Debian/Ubuntu/Raspberry Pi OS and a dnf/yum repo for Fedora & friends, covering both the headless server (`rsplayer`, all architectures) and the desktop app (`rsplayer-desktop`, x86_64). - `install.sh` and `install_desktop.sh` now add the repository and install through the system package manager, so after a one-time install **updates arrive with regular system updates** — `sudo apt upgrade` / `sudo dnf update` — no more re-running the install script for every release. - **Existing installations** keep working as-is; re-run the install script once to switch to the repository. - Direct package download remains as the fallback and is still used for pre-releases, Arch, and distros without apt/dnf. #### Flatpak — Self-Hosted Repository The desktop Flatpak is now published in RSPlayer's own GPG-signed Flatpak repository at [ljufa.github.io/rsplayer-flatpak](https://ljufa.github.io/rsplayer-flatpak) (the planned Flathub distribution has been dropped). One command installs it and registers the remote: ```bash flatpak install https://ljufa.github.io/rsplayer-flatpak/io.github.ljufa.rsplayer.flatpakref ``` - Updates arrive through the normal `flatpak update` flow, with static deltas generated for fast delta downloads and AppStream metadata so the app shows up properly in software centers. - Free of Flathub's sandbox-policy constraints, the Flatpak now grants **direct ALSA device access out of the box** (`--device=all` — bit-perfect exclusive output to USB DACs with no `flatpak override` needed) plus read-only access to `/media`, `/run/media`, and `/mnt` for music on external drives and host mounts. - The repository is rebuilt and re-signed automatically on every release by a dedicated workflow. ### Fixes #### Correct Start Menu Entry for the deb/rpm/arch Desktop Packages The deb, rpm, and Arch tgz desktop packages installed an auto-generated menu entry that showed up as "rsplayer-desktop" with a generic "System — RSPlayer Desktop" description, filed outside the audio category. All native packages now ship the same entry as the Flatpak and Snap: **RSPlayer** — *Hi-fi music player with bit-perfect playback*, in the Multimedia/Audio menu group, with sensible search keywords (music, hifi, dac, flac, dsd, multiroom). Applies on new installs and upgrades. ### Documentation - The documentation site now follows the system color scheme — dark docsify theme and a dark banner variant when the browser prefers dark mode. ## v4.6.0 — 2026-07-10 ### Features #### Platform-Aware Playback Defaults Playback now works on first launch without visiting Settings. On a fresh install RSPlayer detects the platform and packaging environment (Windows, macOS, desktop Linux, Flatpak, Snap, headless systemd server) and picks a working output device and volume control: - **Windows / macOS**: the system default output device with `Software` volume control at a moderate initial level. - **Desktop Linux (including Flatpak and Snap)**: the PipeWire output with the `Pipewire` volume control — first launch **adopts** the current system volume instead of changing it (falls back to `Software` gain where no `wpctl`/`pactl` exists). - **Headless Linux (deb/rpm systemd service)**: the default ALSA device with `Software` volume control. Fixes along the way: - First launch with the `Pipewire` volume control no longer resets the system default sink to 0% (muting the whole desktop). - `Software` volume no longer starts silent at 0 on a fresh install. - The default volume step is now 5 instead of 0. Defaults apply only to fresh installs (or unreadable stored settings) — existing configurations are never touched. ### UI Improvements & Fixes - The **welcome dialog** reflects the new out-of-the-box experience: a "Ready to Play" notice replaces the "Required Setup" warning, the music library is the first step, and picking a DAC directly is recommended for best quality. - **Confirmation dialogs on the Settings page could render off-screen** depending on screen resolution and scroll position (the buttons became unreachable). Dialogs are now always centered in the viewport. Root cause: a `backdrop-filter` on the page container hijacked `position: fixed` positioning; the background blur moved to the artwork layer itself. - **Desktop app**: the restart confirmation now explains that the app will close and will not restart automatically — you need to start it again yourself (there is no systemd to bring it back). ### Documentation & Packaging - The README now has a **"Get it from the Snap Store" button and Flatpak install instructions** for the desktop app, replacing the desktop install script instructions. - New installation docs section for **desktop app sandbox permissions**: `snap connect rsplayer:alsa` / `rsplayer:removable-media`, and `flatpak override` flags for `/mnt` access and direct ALSA output. - New documentation banner. ## v4.5.2 — 2026-07-09 > **No update required.** This is a packaging-only release that adds a Snap of the desktop app — it contains no playback, server, or UI changes that affect existing installations. If you already run RSPlayer, there is nothing to gain by upgrading. ### Snap / Snap Store Support (desktop app) The desktop app is now packaged as a **Snap** for Ubuntu (and other snapd distributions), alongside the existing Flatpak. It ships the same Tauri desktop app under strict confinement; the headless server keeps its deb/rpm/tgz/Docker channels. Packaging lives in `PKGS/snap/` with the manifest at `snap/snapcraft.yaml`. ```bash sudo snap install rsplayer sudo snap connect rsplayer:alsa # bit-perfect direct ALSA output to USB DACs sudo snap connect rsplayer:removable-media # music on external/removable drives ``` - **Strict confinement** with the `gnome` extension. Interfaces: `audio-playback` (PipeWire/PulseAudio), `alsa` (direct `hw:` output — connect manually for bit-perfect), `network`/`network-bind` (embedded server, internet radio, multiroom), `mpris` (media keys, matching the `org.mpris.MediaPlayer2.rsplayer` bus name), `home` and `removable-media` for the music library. - **Sandbox-aware behavior** (same as the Flatpak): inside the snap, network-share mounting and system power actions are hidden — detected via the `SNAP_NAME` environment. Music is readable from `~/Music`, and from removable drives once `removable-media` is connected. App data lives in `~/snap/rsplayer/current/.config/rsplayer`. - **PipeWire in the sandbox**: the "Pipewire" playback device is offered through the PulseAudio compatibility socket, with `pactl` staged so the PipeWire volume control stays available. Bit-perfect exclusive ALSA output to USB DACs works once the `alsa` interface is connected. - **Host compatibility**: the bundled WebKitGTK is kept off `xdg-desktop-portal` (via `GTK_USE_PORTAL=0` plus GIO's non-portal network monitor and proxy resolver), so the webview loads on hosts where the portal cannot verify snap-confined callers. Targeted at Ubuntu; verified working on non-Ubuntu hosts too. - **Build & release**: a new `build_snap` job in `cd.yml` builds the snap on each release and, on tags, uploads it to the Snap Store `candidate` channel (promote to `stable` after a smoke test). New cargo-make tasks for local packaging work: `build_snap`, `install_snap`, and `run_snap`. ## v4.5.1 — 2026-07-08 > **No update required.** This is a packaging-only release for Flatpak support — it contains no playback, server, or UI changes that affect existing installations. If you run v4.5.0, there is nothing to gain by upgrading. ### Flatpak Support (desktop app) The desktop app is now packaged as a Flatpak under the ID `io.github.ljufa.rsplayer` (packaging lives in `PKGS/flatpak/`). - **App identifier** changed from `com.rsplayer.desktop` to `io.github.ljufa.rsplayer` (a GitHub-verifiable ID). The MPRIS bus name is now `org.mpris.MediaPlayer2.rsplayer`. - **Sandbox-aware behavior**: inside the Flatpak sandbox, network-share mounting and system power actions are hidden (they need privileges the sandbox never grants). Music is readable from `~/Music` and removable drives/mounts (`/media`, `/run/media`, `/mnt`); other folders can be granted with Flatseal. Note that symlinks only resolve if their target is also granted. - **PipeWire in the sandbox**: the PipeWire playback device is offered via the runtime's PulseAudio compatibility layer, and the PipeWire volume control falls back to `pactl` where WirePlumber's `wpctl` is unavailable (native installs are unchanged and still prefer `wpctl`). Bit-perfect exclusive ALSA output to USB DACs works normally. - **Release workflow** now pre-builds the web UI once and feeds it to the offline Flatpak build. - New cargo-make tasks for local packaging work: `build_flatpak` (build + install from the working tree), `run_flatpak`, `flatpak_bundle` (single-file bundle for testing on other machines), and `flatpak_cargo_sources`. ## v4.5.0 — 2026-07-05 ### Features #### Synchronized Multiroom Playback (beta) RSPlayer can now play the same music on several devices at once, synchronized to a few milliseconds — with **no external software** (Snapcast, Roon, etc.) and no cloud service. Every RSPlayer instance on the LAN discovers the others automatically, and any of them can stream its playback to the rest. A typical setup: a Raspberry Pi + USB DAC in the living room, another in the kitchen, and a desktop in the office, all grouped from the web UI and playing in sync. Full guide: [Multiroom Playback](multiroom.md). **Setup and grouping** - Enable it per device in **Settings → Multiroom**, give each a room name, and restart. Each device generates a permanent cryptographic identity on first start, so groups survive restarts and IP-address changes. - On the **player page**, a **Multiroom** panel lists discovered rooms; toggling a room on makes it a **follower** of the current device (the **leader**). Followers can join cleanly in the middle of a running track. - While grouped, a follower's transport controls are disabled (the leader drives play/pause/next/seek), but **volume and DSP/EQ stay per-room** — each room applies its own correction to the received stream. Leaving is one click from either side, and a lost connection returns a follower to standalone automatically. **How it stays in sync** - **Discovery** uses mDNS (the zero-config mechanism AirPlay/Chromecast use); transport is **[iroh](https://www.iroh.computer/)** QUIC, dialed by device identity rather than IP. All traffic is end-to-end encrypted and never leaves the LAN. - The leader decodes each track **once** and tees the raw PCM (source rate, pre-DSP) before its own processing, so every follower runs the audio through its own pipeline (resample, EQ, volume, visualizer). Loudness-normalization gain is carried from the leader. Audio travels as 16-bit PCM (~1.4 Mbit/s per follower at 44.1 kHz stereo); 16-bit sources cross the wire losslessly, and the leader's local playback is always untouched. - Three mechanisms hold the group together: **NTP-style clock synchronization** (sub-millisecond on a LAN), **scheduled playback** (every chunk carries the exact instant it must reach the speakers), and continuous **drift correction** (inaudible time-stretching to track each DAC's real position). In practice rooms align to single-digit milliseconds and stay locked for arbitrarily long sessions. **Networking:** requires UDP between devices — RSPlayer's fixed sync port is **UDP 47800**, plus mDNS on **UDP 5353**. See the doc for firewalld/ufw/iptables snippets. **Known limitations:** DSD tracks play on the leader only (they can't traverse the PCM chain); there is no gapless playback while grouped (each track starts a new synchronized session); low-power devices (e.g. Pi Zero) spend noticeable CPU as followers. Works across any mix of Linux, macOS, and Windows. > **Beta.** Sync quality depends on factors outside RSPlayer's control (audio-driver latency reporting, Wi-Fi APs, device firewalls). If it misbehaves, please [open an issue](https://github.com/ljufa/rsplayer/issues/new?template=multiroom_report.md) with the details listed in the doc. #### ASIO Output on Windows Windows builds now support **ASIO** in addition to WASAPI. Installed ASIO drivers appear as `… (ASIO)` entries in Settings → Audio interface and provide exclusive, low-latency, bit-perfect output; the driver's own control panel governs its buffer size and sample rate. The device selection encodes the host as an `asio:` prefix on the stored device name, so the rest of the pipeline is unchanged. Both local playback and multiroom-follower playback route through the selected host. Windows release binaries are linked against the Steinberg ASIO SDK (downloaded at build time — see `docs/build.md`); ASIO is a trademark and software of Steinberg Media Technologies GmbH. ## v4.3.0 — 2026-07-03 ### Bug Fixes #### Song Endings Cut Off by up to the Ring Buffer Length When a song finished naturally, the audio output was paused immediately, discarding whatever was still queued in the ring buffer — with the default `ring_buffer_size_ms` of 1000 that meant the last ~1 second of every track was never played. The output now **drains** the ring buffer at the natural end of a song before pausing, so tracks play out fully. Skipping tracks and stopping remain instant (drain aborts immediately on a stop request, on output errors, or after a safety deadline). #### Play/Pause Broken After Queue Finishes The play/pause toggle used the internal stop flag to decide between play and pause. After the queue finished naturally the flag was stale, so the first press of play/pause did nothing and only the second press restarted playback. Playback state is now derived from the actual playback thread (`is_finished()`). Related hardening in the same area: sending `Play` while already playing no longer spawns a second concurrent playback thread writing to the same device, and `play_from_beginning` now defensively stops any running playback first. #### Silent Audio Drop on Stalled Output If the audio device stalled without reporting errors (e.g. suspended/hung USB device), ring-buffer writes timed out after 1 second and the unwritten samples were silently discarded while decoding continued — playback degraded into silent sample-dropping. A write timeout is now treated as an output error and flows into the existing reopen/retry machinery. #### Queue Search Pagination Returned Wrong Pages and Totals With a search term active, queue pagination applied the offset to the *unfiltered* queue and reported the total size of the whole queue instead of the match count. Paging through search results could skip or duplicate entries. The filter is now applied before offset/limit and the reported total is the exact number of matches. Additionally, 1–2 character search terms now actually filter (previously they were silently ignored), and the *Load more* button is hidden when there are no further results. #### Saved External Mount Not Shown Until Page Refresh (Settings) Clicking **Save** on a detected external network mount persisted it on the backend, but the managed-mounts and music-directories lists on the Settings page kept rendering the stale settings snapshot fetched at page load. The page now re-fetches settings when the external-mounts event arrives, so the saved mount appears in the lists immediately. #### Like Button Icon Not Updating (Player Page) Liking/unliking the current song persisted the change but the heart icon only updated after a track change or page reload. The backend now re-broadcasts the current song with fresh statistics after a like/dislike affecting it, so all connected clients update the icon immediately. #### VU Meter Fixes - The meter was permanently lost for the rest of the song when the audio output was reopened after a write failure or a sample-rate fallback. It now survives output reopens. - With software volume control the meter scaled peaks linearly by `vol/255` although the volume range is 0–100 and the applied gain is cubic — the meter read ~2.5× too low and tracked the wrong curve. It now applies the same cubic `(vol/100)³` gain as the output path, and shows source amplitude when a hardware volume control is active (hardware attenuation happens after the DAC and is not visible to a digital meter). #### Audio Format Info Missing Until Next Track ("No file playing") Opening the web UI while a song was already playing showed "No file playing" in place of the codec / bit-depth / sample-rate line (and no loudness info) until the next track started, because the player info event is only broadcast when decoding begins. The server now caches the current player info and sends it to clients on connect, so the format line is populated immediately. #### Broken Artwork on "Most Played" and "Liked" Cards The Favorites cards on the Playlists page showed broken-image icons: the backend sent the image as `/artwork/{id}` and the frontend prefixed `/artwork/` again, producing a `/artwork//artwork/{id}` request that 404'd. The backend now sends the bare artwork id, consistent with album cards. #### Queue Reorder Acted on the Wrong Song in Filtered Views With a queue search filter (or Focus view) active, drag-and-drop reordering and **Play Next** computed absolute queue positions from the filtered list — so they silently moved the *wrong* song. The move commands are now addressed by song id instead of queue index, which is correct in any view. Covered by new unit tests. #### Missing-Artwork Placeholder Invisible on Light Themes The "no album art" placeholder was drawn in white at 15% opacity, making it essentially invisible on light themes. It now uses a theme-agnostic mid-tone with an indigo spindle accent that reads on both light and dark card backgrounds. #### Playback Robustness - An audio device whose default sample format is unsupported (e.g. `U8`, `F64`) caused a panic in the playback thread; it now returns an error through the normal fallback/retry path. - An ICY radio stream advertising `icy-metaint` whose metadata extraction failed caused a panic; it now falls back to a plain (metadata-less) stream reader. - The state-persistence task now exits cleanly when the event channel closes (previously it busy-looped at 100% CPU) and logs when it lags behind the event stream. - Removed the "Playing" / "Playback paused" toast that appeared on every automatic track transition — playback state is already visible in the UI; error notifications are unchanged. ### Improvements - **Faster song lookup in large queues** — playing or removing a song by id deserialized every queue entry. A byte-level prefilter now skips non-matching entries without parsing, making play-by-click near-free on multi-thousand-song queues. - **Faster queue clear/replace** — clearing and replacing the queue removed entries one by one; they now use the database's native keyspace clear, and bulk inserts go through a single write batch. - **Higher-quality EQ on resampled output** — when resampling to an integer device format, the equalizer and VU meter processed already-converted integer samples (double quantization). They now run in the f32 domain before the final conversion. ### Visual Identity - **New logo** — an analog VU-meter mark ("Meter") replaces the old RSP lettering, with a single SVG master (`docs/_assets/logo.svg`) driving every asset. - **README banner** — light/dark wordmark lockups (text converted to vector paths, so they render identically everywhere) shown via GitHub's theme-aware `` switching. - **Documentation site** — the docsify sidebar now shows the banner lockup, the site has a favicon, and the landing page title states what RSPlayer is instead of "Home". - **Application icons** — all desktop/mobile icon sets (Linux PNGs, Windows `.ico` + Store logos, macOS `.icns`, Android/iOS) regenerated from the new mark via `cargo tauri icon`, plus a new multi-resolution web UI `favicon.ico`. ### Internal - Deduplicated ~150 lines of device-probing code (`find_device_rate`, `find_device_channels`, `fallback_rate_candidates`) that existed in both `alsa_output.rs` and `device_capabilities.rs`; `device_capabilities.rs` is now the single implementation. - Removed an unnecessary `unsafe impl Send for PlaybackResult` and a dead `BrowseItem::Station` variant in the radio page. - Random-mode history index widened from `u16` to `u32` (no more silent wrap after 65k skips) with endian-stable keys. - Consolidated duplicated last-played-time parsing in `PlayerService`. - Fixed a latent test-harness bug where the queue test database directory was deleted while still open, masking database errors in tests. --- ## v4.2.0 — 2026-06-30 ### New Features #### Windows Support (experimental) RSPlayer now builds and runs on Windows. Two artifacts are produced: - **Headless server** (`rsplayer_windows_amd64.exe`) — a standalone executable you can run directly from a terminal or schedule as a Windows service. No installation needed. - **Desktop installer** (`rsplayer-desktop_windows_amd64.exe`) — an NSIS installer that bundles the Tauri desktop app and automatically installs [WebView2](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) if it is not already present (included with Windows 10/11 and Edge). Audio output uses WASAPI via `cpal`. The web UI is served at `http://localhost:8000` and works in any browser (see troubleshooting.md if Edge shows a blank page — this is an Enhanced Security Mode issue, not an RSPlayer bug). The CRT is statically linked (`-C target-feature=+crt-static`) so the binary has no dependency on `VCRUNTIME140.dll`. Platform limitations on Windows: network share mounting, ALSA/PipeWire volume, IR remote, system poweroff/reboot, and firmware USB integration are unavailable. #### Media Key Bindings in Desktop App The desktop application now registers with the OS media session, so hardware media keys and OS-level controls (lock screen, Bluetooth headset, keyboard media row) work out of the box: | Key / Event | Action | |---|---| | Play / Pause / Toggle | Toggle play/pause | | Next | Next track | | Previous | Previous track | | Stop | Stop | | Set volume | Set volume (0–100) | On **Linux** this uses MPRIS2 (D-Bus), on **macOS** the native MediaRemote framework, and on **Windows** the GlobalSystemMediaTransportControls API — all via the `souvlaki` crate. The media session is registered for the lifetime of the app; no configuration is needed. #### Remote Access URL in Desktop Settings When running as a desktop app, the Settings page now shows a **Remote access** link below the version string. It displays the machine's local network address (e.g. `http://192.168.1.42:8000`) so you can open the web UI from a phone or another device on the same LAN without having to look up the IP manually. ### Improvements #### UX — First-Time Setup and New-User Guidance ([#20](https://github.com/ljufa/rsplayer/issues/20)) Several changes improve the out-of-the-box experience for new users, especially on the desktop app. **Welcome modal expanded with setup steps** The first-time welcome modal now walks through a structured setup checklist: 1. **Audio Device** (required) — direct link to Settings to select a playback device. 2. **Music Library** (required) — configure local or network music directories. 3. **Network Mounts** (optional, shown only on Linux/ALSA builds) — brief explanation that network share configuration is only available in the headless server install. 4. **Internet Radio** (optional, shown only where supported) — explains that internet radio requires a network-connected server build and is unavailable in the desktop app. 5. **Start Listening** — confirms setup is complete. This directly addresses the confusion reported in [#20](https://github.com/ljufa/rsplayer/issues/20): users installing the desktop app before the server expected radio stations and network mounts to work, but those features are only available in the headless server. **UI preferences moved from browser localStorage to backend database** Theme, visualizer style, background image toggle, and the "welcome shown" flag are now stored in the backend database via the existing settings API, rather than `localStorage`. This fixes the welcome modal never appearing in the Tauri desktop app, where the webview does not reliably persist `localStorage` across restarts. Preferences are now fully synchronized with the server and survive app restarts on all platforms. **Radio — Favorites empty state now guides users to browse** When the Favorites tab is empty, the Radio page now shows a descriptive empty state pointing to the Top/Country/Language/Search tabs instead of a blank list. Previously the empty state never rendered at all because the loading flag was not cleared for empty favorites. #### Remote Access URL — Copy Button and Improved Description The remote access URL shown at the bottom of the Settings page (desktop mode) has been improved: - The URL is now displayed as **plain text** instead of a clickable link — clicking the link previously redirected the Tauri webview away from the app. - A **copy button** (clipboard icon) is shown next to the URL for easy copying to another device. - The description now reads *"Open on another device (phone, tablet, browser):"* to make the purpose immediately clear. #### Desktop App — Default Port Changed to 8001 The desktop application now starts on port **8001** by default (previously 8000). This avoids an accidental port conflict if the standalone headless server binary is also run on the same machine without a custom `PORT` environment variable — both previously defaulted to 8000. The server install (deb/rpm/systemd) continues to use port 80 via the `env` file and is unaffected. ### Internal / Build - **cpal upgraded to 0.18.1** — the audio output library has been updated to 0.18.1. The fork's custom patches (DSD native output, ALSA buffer-size constraint fix, high-sample-rate support) have been rebased onto the new branch (`v_0.18.1_patched`). - **Cross-platform signal handling** — `tokio::signal::unix` (`SIGTERM`) is now wrapped in `#[cfg(unix)]` so the server crate compiles cleanly on Windows. On non-Unix platforms the terminate-signal future resolves never (`std::future::pending`), and Ctrl+C continues to work via `tokio::signal::ctrl_c` on all platforms. - **Windows CI job** (`build_desktop_windows`) added to `cd.yml`. Runs on `windows-latest`, installs `tauri-cli`, builds the headless `.exe` and the NSIS installer, and uploads both as release artifacts. Dispatch target `windows` is available alongside `macos`, `all`, and the Linux per-arch targets. - **Comparison table** in `README.md` extended with Language, Playback engine, OS support, and Native desktop app variant rows. --- ## v4.1.0 — 2026-06-23 ### New Features #### Configurable Bind Address The server's listen address is now controlled by the `BIND_ADDR` environment variable (default `0.0.0.0`). Previously the address was hardcoded to bind on all interfaces. Setting `BIND_ADDR=127.0.0.1` restricts the server to loopback only — useful when running behind a reverse proxy or in an environment where external exposure is undesirable. The variable is present in `/opt/rsplayer/env` with its default value and is applied consistently to the HTTP listener, the HTTPS listener, and the degraded-mode fallback server. ### Improvements #### Desktop Mode — Settings Page Cleaned Up Two settings sections are now hidden when the desktop app is running: - **RSPlayer firmware control channel** (USB section) — irrelevant on a desktop host that has no attached RSPlayer firmware. - **System** (power controls — Restart RSPlayer, Restart system, Shutdown) — these system-level commands target the headless server use-case; they have no meaning in the desktop context. The version string that was previously embedded inside the System section is now shown unconditionally at the bottom of the settings page, so it remains visible in all modes. The desktop app now sets the `RSPLAYER_DESKTOP` environment variable at startup, which the backend reads and exposes as the `desktop_mode` flag in the `Settings` response. The web UI uses this flag to conditionally render the sections above. ### Internal / Build - `composition_root::build()` renamed to `build_app_container()` for clarity. - New `run_desktop_dev` Makefile task: builds the release UI, copies `loading.html`, and runs `cargo tauri dev` — a single command for local desktop development iteration. - `install.sh`: downloaded package files are now `chmod 644` after a successful download, ensuring consistent permissions across package managers. - Removed a stale `#[allow(clippy::redundant_pub_crate, clippy::too_many_lines)]` attribute from `main.rs`. --- ## v4.0.1 — 2026-06-19 ### New Features #### Desktop Arch Linux Package RSPlayer Desktop is now available as a `.tgz` package for Arch Linux (and derivatives like Manjaro): - `rsplayer-desktop__amd64.tgz` is produced alongside the existing deb and rpm desktop packages. - The tarball includes the `rsplayer-desktop` binary, a `.desktop` entry, and icons for 32px, 128px, and 256px. - A `PKGBUILD` and install script are placed alongside the tarball for users who prefer to build from source or inspect the dependency list. - `install_desktop.sh` detects Arch-based distros, installs runtime dependencies (`webkit2gtk-4.1`, `gtk3`, `libappindicator-gtk3`, `librsvg`, `alsa-lib`), extracts the tarball, and updates the icon cache. Platform coverage: | Platform | Architectures | Package Format | |----------|--------------|----------------| | Linux | x86_64 | `.deb`, `.rpm`, `.tgz` | | macOS | Apple Silicon, Intel | `.dmg` | #### New Desktop Installer Script A new `install_desktop.sh` script is the recommended way to install the desktop application on any supported platform. It auto-detects the distribution, downloads the correct desktop package from the latest GitHub release, and installs it with the appropriate package manager — separate from `install.sh` (which remains the headless server installer). ### Bug Fixes #### Desktop Packages Missing Icons (deb/rpm) The `tauri.conf.json` was missing the `icon` field in the `bundle` section. Without it, Tauri's bundler did not include any icon files in the generated `.deb` and `.rpm` packages, so the application appeared without an icon in the system menu. Fixed by adding `icons/32x32.png`, `icons/128x128.png`, `icons/128x128@2x.png`, and `icons/icon.png` to the bundle configuration. #### Desktop App — "asset not found: loading.html" The desktop application referenced `loading.html` (the startup splash screen) from Tauri's `frontendDist`, but the file was never copied into the frontend distribution directory. The `bundle_desktop_release` Makefile task now copies `crates/desktop/loading.html` to `dist/web-ui/` before invoking `cargo tauri build`. #### Server Installer — Ubuntu Downloading Desktop Package `install.sh` was downloading the desktop `.deb` (`rsplayer-desktop_*.deb`) instead of the server `.deb` (`rsplayer_*.deb`) on Ubuntu/Debian systems because the grep pattern `_.deb` matched both asset names. The asset filter now explicitly excludes paths containing `/rsplayer-desktop_`. ### Internal / Build - The `bundle_desktop_release` Makefile task now uses absolute paths (`$(pwd)/../../...`) and runs the tarball creation in a subshell, preventing working-directory changes from breaking subsequent commands. - CI job name updated to reflect the additional arch tgz output. --- ## v4.0.0 — 2026-06-18 ### New Features #### Desktop Application (Tauri) RSPlayer now ships as a standalone desktop application alongside the headless server: - **Native window** built with [Tauri 2](https://v2.tauri.app/) — deb and rpm packages for Linux (`rsplayer-desktop_*_amd64.deb`, `rsplayer-desktop-*.x86_64.rpm`), and a DMG for macOS. - The desktop app embeds the same RSPlayer backend (from `crates/server/src/lib.rs`) and runs it as a child tokio task inside the Tauri application process. - On startup the window shows a loading screen (`loading.html`) while the backend starts up; once the HTTP server is ready it redirects to the web UI. - Graceful shutdown: closing the desktop window sends a shutdown signal to the backend, triggering a clean database `SyncAll` persistence. - If the configured port (default `8000`) is already in use, the app probes sequential ports up to `9000` before falling back to an OS-assigned port. - A new `install_desktop.sh` script detects the platform (Debian/RPM/macOS) and downloads the appropriate desktop package from the latest GitHub release. The web UI now shows a **"Connecting to server…"** full-screen overlay with a spinner while the WebSocket is not yet connected — essential for the desktop build where the backend starts asynchronously in the same process. **Desktop is available for:** | Platform | Architectures | Package Format | |----------|--------------|----------------| | Linux | x86_64 only | `.deb`, `.rpm` | | macOS | Apple Silicon, Intel | `.dmg` | > The headless server packages (`rsplayer_*` — deb, rpm, Arch tgz, raw binary) continue to be produced for all architectures and are unaffected. ### Internal / Build & Release Pipeline #### Packaging overhaul — standard tooling instead of hand-rolled scripts The release pipeline was simplified and made more robust: - **Version single source of truth** moved from `Makefile.toml` to `[workspace.package]` in the root `Cargo.toml`. `cargo-deb`, `cargo-generate-rpm`, `cargo-packager` and the application itself (`env!("CARGO_PKG_VERSION")`) all read it natively. CI fails fast if a release tag doesn't match the workspace version. - **RPM packages** are now built with `cargo-generate-rpm` (declarative config in `crates/server/Cargo.toml`) instead of a hand-rolled `rpmbuild` spec-template pipeline. The old `PKGS/rpm/rsplayer.spec.in` was removed. Install/uninstall scriptlets and file lists are unchanged. - **macOS** server builds (`rsplayer_darwin_arm64`/`rsplayer_darwin_amd64`) and desktop DMGs are now built **natively on GitHub macOS runners**. The osxcross-based darwin cross-compilation Docker images and entry-point scripts were removed. - **All server packages** and raw binaries keep their previous release asset name patterns; `install.sh` and the Docker publish flow are unaffected. #### Docker Builder Image Consolidation - The separate `rsplayer-backend-builder` and `rsplayer-ui-builder` Docker images are merged into a single `rsplayer-builder` image (`docker/Dockerfile.builder`), which includes everything needed for both the web UI and backend/desktop builds (Rust with wasm32 target, Node.js, Tailwind CLI, cross, cargo-deb, cargo-generate-rpm, cargo-packager, tauri-cli, dioxus-cli, cargo-make, and all Linux desktop/WebKit dev libraries). - The legacy `docker/Dockerfile.backend` was removed. - All darwin cross-compilation images (`cross-aarch64-apple-darwin`, `cross-x86_64-apple-darwin`) were removed from `build-images.yml`. #### CI/CD Consolidation - The `cd.yml` workflow was rewritten: it now supports `workflow_dispatch` with per-target selection, verifies the pushed tag matches the workspace version, and produces all release assets (deb, rpm, Arch tgz, binary) via the new `cargo make package_linux_release` task. - A new `build_desktop_linux` job produces deb+rpm desktop packages via Tauri. - The legacy `copy_remote_mac` Make task was removed (macOS builds are now done natively). ### Improvements #### Server Refactored for Embedding The server binary logic was extracted from `main.rs` into a new `lib.rs` (`crates/server/src/lib.rs`) exposing a public `run_backend()` function. This allows the desktop application (and any future embedders) to start the server as a library rather than spawning a subprocess. The `build.rs` that previously parsed the version from `Makefile.toml` and embedded `index.html` via a manual `include_str!` was removed — the version now comes from `env!("CARGO_PKG_VERSION")` and `index.html` is served directly from the `RustEmbed`-based `StaticContentDir`. #### Rust Edition 2024 The `crates/desktop` crate uses the Rust 2024 edition. The server and other workspace crates were updated to edition 2024 as well. #### Rustfmt Line Width Extended The project-wide `max_width` was increased from 120 to 140 characters. #### Documentation Site Overhaul - New **logo** (`docs/_assets/logo.svg`) with an engraved "RSP" hexagon design. - Docsify theme switched from `buble` to `vue`, with updated styling for logo placement and inline images. - **Full-text search** plugin (`docsify-search`) added to the documentation site. - **Image zoom** plugin (`docsify-zoom-image`) added. - `installation.md` rewritten with a supported-platforms table (Server vs Desktop per architecture), explicit release filename suffixes, and separate macOS server/desktop instructions. ### Removals - **Darwin cross-compilation infrastructure** — `docker/Dockerfile.cross-aarch64-apple-darwin`, `Dockerfile.cross-x86_64-apple-darwin`, `docker/darwin-entry.sh`, and `docker/darwin-x86_64-entry.sh` removed (macOS is now built natively). - **`PKGS/rpm/rsplayer.spec.in`** — replaced by `cargo-generate-rpm` declarative config in `crates/server/Cargo.toml`. --- ## v3.5.5 — 2026-05-30 ### Bug Fixes #### Library/Artists — Orphaned Album Entries Left After File Deletion Removing music files from disk and running "Update Library" (incremental scan) left empty album entries in the artist view. The tracks were correctly removed from the database, but the album record was never cleaned up — albums with zero songs remained visible in the Library/Artists tree with no playable content. **Root cause:** When the scanner detected a file was deleted, it only removed the entry from the `songs` keyspace. The corresponding `albums` keyspace was never updated: the album's `song_keys` list retained the deleted track's file path, and albums that became empty were never deleted. **Fix (two parts):** 1. **Per-track album cleanup during scan** — Before deleting a song from the database, the scanner now reads its metadata, removes the file path from the owning album's `song_keys`, and deletes the album entry if `song_keys` becomes empty. This handles all future incremental scans. 2. **One-shot migration for existing databases** — On first startup after upgrading to 3.5.5, `MetadataService::new()` runs a migration that iterates every album in the database, strips any `song_keys` entries referencing songs that no longer exist, and removes albums that become empty. A persistent marker is stored in the `_migrations` keyspace so this runs exactly once. > ⚠️ **Action required:** The migration runs automatically on first startup. No manual rescan is needed — existing orphaned albums will be cleaned up immediately. Users who prefer to trigger a full rescan (Settings → Rescan — Full) will also see the same result. --- ## v3.5.4 — 2026-05-29 ### Internal / Architecture #### Project Structure Restructured — Flat to `crates/` Layout The source tree has been reorganized from a flat list of crate directories to a standard `crates/` workspace layout: | Before | After | |--------|-------| | `rsplayer_backend/` | `crates/server/` | | `rsplayer_playback/` | `crates/playback/` | | `rsplayer_metadata/` | `crates/metadata/` | | `rsplayer_hardware/` | `crates/hardware/` | | `rsplayer_config/` | `crates/config/` | | `rsplayer_dsp/` | `crates/dsp/` | | `rsplayer_wire/` | `crates/wire/` | | `rsplayer_api_models/` | `crates/api_models/` | | `rsplayer_web_ui/` | `web-ui/` (workspace member) | Every crate's internal module structure and public API are unchanged — this is purely a file-system reorganization. All `Cargo.toml` workspace paths, `Cross.toml` volume mounts, and CI workflow paths have been updated to match. #### `build.rs` for Version Propagation Version is now centralized in `Makefile.toml` (`RELEASE_VERSION`) and propagated into the server binary at compile time via a `build.rs` in `crates/server/`. The old `rsplayer_backend/build.rs` has been removed. The build script also embeds `index.html` from the UI release build directly into the server binary. #### Makefile and CI Consolidation - `Makefile.toml` streamlined — build tasks reorganized, redundant targets removed. - `web-ui/Makefile.toml` removed; web UI tasks merged into the root `Makefile.toml`. - CI/CD workflow updated for the new directory layout (`crates/server/`, `web-ui/`). - Docker images and `Cross.toml` updated with corrected paths. #### `filters.rs` Refactored The biquad filter implementation in `crates/dsp/src/filters.rs` received a structural cleanup — coefficient computation was factored into reusable methods, and shared math was deduplicated across filter types. ### Improvements #### Frontend Cross-Arch Build Support Player page now supports a `cross_arch_build` feature toggle that allows disabling platform-specific widget rendering, making the frontend buildable for broader WASM targets. --- ## v3.5.0 — 2026-05-22 ### New Features #### macOS Build Target Support (Apple Silicon + Intel) RSPlayer now has cross-compilation targets for macOS: - `aarch64-apple-darwin` - `x86_64-apple-darwin` Build and release pipeline support was added across `Cross.toml`, custom cross Docker images, and GitHub Actions. Tag releases now produce darwin binary artifacts alongside Linux outputs. ### Improvements #### Software Volume Control Path A new software volume backend (`VolumeCrtlType::Software`) was added and integrated end-to-end: - New software gain volume device with `0..100` range and 5-step increments. - Software volume level is shared via atomic state and initialized from saved settings at startup. - PCM attenuation is applied in the output callback using a perceptual cubic curve `(vol/100)^3`, so volume changes take effect with output-buffer latency instead of ring-buffer latency. - Volume-control options are now platform-aware in settings. Linux keeps ALSA/Pipewire/software/off options (based on build features); non-ALSA builds expose software/off. #### Non-Linux Audio Device and Settings UX - Backend now enumerates output devices via `cpal` when ALSA is not enabled and exposes a stable `System Default` output entry. - Settings UI now consumes backend-provided available volume control types instead of hardcoded enum iteration. - ALSA-only controls (for example, ALSA buffer size) are hidden when ALSA is unavailable. - Changing audio card now auto-selects the first PCM device to avoid empty output-device names. #### CI/CD and Tooling for Darwin Targets - Added darwin targets to release workflow matrix. - Linux packaging remains unchanged (`.deb`, `.rpm`, `.tgz`), while darwin targets publish binary artifacts only. - Added two new builder-image jobs in `build-images.yml`: - `rsplayer-cross-aarch64-apple-darwin` - `rsplayer-cross-x86_64-apple-darwin` - `build_release` now applies `--no-default-features` only for darwin targets. ### Bug Fixes #### Network Mount Responsiveness on Unavailable Shares Network-mount behavior was hardened to avoid long blocking stalls when remote storage is unavailable: - NFS mounts now use bounded retry/timeout options (`soft,timeo=50,retrans=2`). - SMB mounts now preflight TCP connectivity to port 445 with a short timeout and mount with `soft` behavior. These changes significantly reduce the time spent waiting on unreachable network shares and improve overall player responsiveness. #### Platform Compatibility and Runtime Safety - Mount service is now Linux-specific, with a non-Linux stub implementation to keep non-Linux builds functional. - Network mount management UI is hidden when mounts are not supported by the running platform. - Poweroff/reboot commands now return a user-facing error notification on non-Linux platforms instead of attempting unsupported system commands. - Rustls provider initialization was made explicit (`ring` default provider install) to match `axum-server` TLS configuration. - Database persistence on shutdown was tightened from `SyncData` to `SyncAll`. #### Metadata Scanning on 32-bit Architectures Fixed a crash when scanning large audio files (> 2 GB) on 32-bit ARM targets (`arm-unknown-linux-gnueabihf`, `armv7-unknown-linux-gnueabihf`). The file-size check used `u32` arithmetic that wrapped around, causing the scanner to misclassify valid files as corrupt and abort the scan. The affected field in `metadata_service.rs` is now widened to `u64`. ### Internal / Architecture #### Symphonia Upgraded to 0.6.0 The audio decoding engine has been updated from the project's custom Symphonia fork (based on 0.5.x) to the official [Symphonia 0.6.0](https://github.com/pdeljanov/Symphonia) release, rebased on top of it. - The custom patches retained from the fork are: - **FLAC channel-count validation** — guards the FLAC decoder against a subframe decode panic when a stream advertises more channels than are present (upstream regression not yet ported to the 0.6.x line). - **MP3 demuxer infinite-loop guard** — breaks out of the strict-frame-sync loop after 10 consecutive failures on corrupted streams. - API changes absorbed internally: `AudioDecoder::decode_ref` / `PacketRef`, new required `FormatReader::media_info`, `SeekTo::Timestamp` rename. These affect only the project's custom APE, DSD, and SACD demuxers/decoders and have no visible effect on playback behaviour. #### cpal Upgraded to 0.17.3 The audio output library has been updated to [cpal 0.17.3](https://github.com/RustAudio/cpal). The fork's custom patches (DSD native output, ALSA buffer-size constraint fix, high-sample-rate support) have been rebased onto the upstream release tag. #### CamillaDSP Replaced with Self-Contained Filter Implementation The [CamillaDSP](https://github.com/HEnquist/camilladsp) library dependency has been removed. RSPlayer only used a small subset of CamillaDSP — biquad filters and a gain stage — and the library unconditionally links `alsa-sys 0.3.x` on Linux, which conflicts with the `alsa-sys 0.4.x` required by cpal 0.17.x. The DSP subsystem (`rsplayer_dsp`) now contains a self-contained implementation of all required filter types: - Biquad filters: Highpass, Lowpass, Bandpass, Notch, Allpass, Peaking EQ, High/Low shelf (Q and slope variants), first-order High/Lowpass and shelf variants, Linkwitz Transform. - Gain filter with linear/dB/mute/invert modes. - Direct Form II Transposed biquad with subnormal flushing. Formulas are taken directly from the Audio EQ Cookbook (R. Bristow-Johnson). The public API of `rsplayer_dsp` is unchanged. This change removes approximately 560 lines from `Cargo.lock` and eliminates the `alsa-sys` version conflict entirely. #### Cross-Compilation Fix — `libLLVM` in Docker Containers Fixed a build failure when cross-compiling for ARM targets using the project's custom `ghcr.io/ljufa/rsplayer-cross-*` Docker images. The `libc` build script spawns `rustc` as a subprocess; inside the container `$RUSTC` resolves to the real compiler binary rather than the rustup wrapper, so `LD_LIBRARY_PATH` was never set and the Rust toolchain's `libLLVM.so` could not be found. `Makefile.toml` now evaluates the active toolchain's sysroot at build time and passes the lib directory into the container via `CROSS_CONTAINER_OPTS`, mounting it at the same host path and setting `LD_LIBRARY_PATH`. No Docker image rebuilds are required. --- ## v3.0.0 — 2026-05-05 ### Breaking Changes #### Firmware Protocol — Binary Wire Format (postcard + COBS) The USB serial protocol between RSPlayer and the hardware firmware has changed from a human-readable text format (e.g. `SetTrack(title|artist|album)\n`) to a typed binary format using [postcard](https://docs.rs/postcard) serialization with COBS framing. The new protocol is defined in the shared `rsplayer_wire` crate (a `no_std` library used by both the host and the firmware). All messages are now strongly-typed (`HostToFw` / `FwToHost` enums), length-bounded with `heapless::String`, and framed at up to 256 bytes per packet. > ⚠️ **Action required:** Firmware must be updated to the matching version that speaks the new binary protocol. Running v3.0.0 against old firmware will result in garbled communication. --- ### New Features #### Browser Audio Playback RSPlayer can now stream audio directly to the browser over HTTP, without requiring an ALSA output device on the server. This makes it practical to run RSPlayer on a headless server or NAS and listen through any browser tab. - Select **Browser** in the audio output dropdown (Settings → Audio Output) to switch to browser playback mode. - The browser creates a hidden `