Android Rust music player with modern features
  • Kotlin 45.8%
  • Rust 26.6%
  • C 14.8%
  • Svelte 6.4%
  • TypeScript 3.9%
  • Other 2.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Lowara1243 099c0ba349
All checks were successful
Build & Release / Build Desktop (Linux) (push) Successful in 7m37s
Build & Release / Build Android APK (ARMv8 / ARMv7) (push) Successful in 13m10s
Build & Release / Build Desktop (Windows x86_64, cross from Linux) (push) Successful in 7m56s
fix(ui): prevent Track # wrap, unify LRC editor playback bar without not playing label
2026-10-03 23:24:24 +02:00
.cargo Add .cargo/config.toml 2026-08-12 20:57:24 +03:00
.forgejo/workflows fix(ci): use Forgejo-compatible upload-artifact action 2026-09-12 07:35:44 +03:00
.gitea/workflows fix(ci): use Forgejo-compatible upload-artifact action 2026-09-12 07:35:44 +03:00
.github/workflows ci: add desktop build jobs, Gitea/Forgejo workflows, and update CI packaging 2026-09-10 15:06:33 +03:00
android fix(ui): prevent Track # wrap, unify LRC editor playback bar without not playing label 2026-10-03 23:24:24 +02:00
desktop fix(metadata, queue, stream): prevent active stream rollback, atomic queue range move, instant metadata sync 2026-10-03 22:48:37 +02:00
examples bench: add accurate benchmark & profiling scripts and update methodology in BENCH.md 2026-09-05 16:25:40 +03:00
exobench bench: add accurate benchmark & profiling scripts and update methodology in BENCH.md 2026-09-05 16:25:40 +03:00
native/opus build(native): add prebuilt Opus libraries for ARMv7 Android and Windows MSVC 2026-09-10 15:05:43 +03:00
scripts chore(scripts): include ~/.local/bin in PATH for pnpm 2026-09-12 18:00:49 +03:00
src fix(metadata, queue, stream): prevent active stream rollback, atomic queue range move, instant metadata sync 2026-10-03 22:48:37 +02:00
.env.example Add .env.example template for WebDAV test harness 2026-08-15 06:53:04 +03:00
.gitignore ci: add desktop build jobs, Gitea/Forgejo workflows, and update CI packaging 2026-09-10 15:06:33 +03:00
BENCH.md bench: add accurate benchmark & profiling scripts and update methodology in BENCH.md 2026-09-05 16:25:40 +03:00
build.sh fix(build): dynamically resolve JAVA_HOME with fallback candidates 2026-10-03 23:11:27 +02:00
Cargo.lock feat(queue, metadata): allow reordering queue groups, defer active stream tag writes, instant metadata updates and pastel tag dialog 2026-09-30 12:12:37 +02:00
Cargo.toml feat(queue, metadata): allow reordering queue groups, defer active stream tag writes, instant metadata updates and pastel tag dialog 2026-09-30 12:12:37 +02:00
README.md fix: address Android queue & Desktop LRC, UI and glow bugs, document desktop builds 2026-09-12 17:53:29 +03:00
TODO.md feat(ui): switch all UI icons to Phosphor Regular 2026-09-05 17:05:45 +03:00

RS Music Player

Cross-platform music player: high-performance Rust core (decoding, DSP, library, networking) behind a UniFFI FFI boundary, Jetpack Compose UI on Android, and a modern Tauri + SvelteKit desktop application for Linux & Windows.

Prerequisites

General & Rust

  • Rust toolchain (stable 1.80+)
  • cargo test for running core tests

Desktop (Linux & Windows)

  • Node.js (20+) and pnpm (corepack enable pnpm or npm install -g pnpm)
  • Linux system dependencies (WebKit2GTK, ALSA, AppIndicator):
    • Ubuntu / Debian:
      sudo apt update && sudo apt install -y \
        libwebkit2gtk-4.1-dev build-essential curl wget file \
        libxdo-dev libssl-dev libayatana-appindicator3-dev \
        librsvg2-dev libasound2-dev pkg-config
      
    • Arch Linux / Manjaro:
      sudo pacman -S webkit2gtk-4.1 base-devel curl wget file \
        xdotool openssl libayatana-appindicator librsvg alsa-lib pkgconf
      
    • Fedora:
      sudo dnf install webkit2gtk4.1-devel @development-tools curl wget \
        libxdo-devel openssl-devel libayatana-appindicator3-devel \
        librsvg2-devel alsa-lib-devel pkgconf-pkg-config
      
  • Windows Cross-compilation from Linux (optional):
    • cargo install cargo-xwin
    • rustup target add x86_64-pc-windows-msvc
    • Note: Prebuilt opus.lib for MSVC is already bundled under native/opus/x86_64-pc-windows-msvc/.

Android

  • cargo-ndk (cargo install cargo-ndk)
  • Android NDK (e.g. /opt/android-ndk or set via ANDROID_NDK_HOME)
  • Android SDK / JDK (OpenJDK 17+)
  • Prebuilt Opus in native/opus/ (included in repository)

Building

Desktop Application (Linux & Windows)

1. Linux Desktop (Native)

To compile the release binary and bundle packages (.deb, .rpm, .AppImage):

./scripts/build-linux.sh

Or manually using pnpm:

cd desktop
pnpm install --frozen-lockfile
NO_STRIP=1 pnpm tauri build
  • Binary output: dist/rs-player-linux (or desktop/src-tauri/target/release/desktop)
  • Package bundles: desktop/src-tauri/target/release/bundle/ (deb / rpm / appimage)

To launch the desktop application in development mode (with live hot-reload):

cd desktop
pnpm install
pnpm tauri dev

2. Windows Desktop (Cross-compile from Linux)

You can cross-compile a standalone Windows executable directly from Linux without a Windows VM using cargo-xwin:

./scripts/build-windows.sh
  • First run: automatically downloads the Windows SDK and CRT into ~/.cache/cargo-xwin (~1 GB).
  • Binary output: dist/rs-player-windows.exe (statically linked, self-contained single .exe).

3. Windows Desktop (Native on Windows)

On a Windows PC with Node.js and Visual Studio C++ Build Tools installed:

cd desktop
pnpm install
pnpm tauri build

Android Application (APK)

Standard Build (All Default Features)

./build.sh        # host lib → Kotlin UniFFI bindings → Android APK (arm64-v8a) → install
cargo test        # Rust integration tests

Feature Flags

The Rust core is modular and supports compile-time feature flags to reduce binary size and memory footprint:

Feature Description Heavy Dependencies
webdav WebDAV cloud streaming & metadata sync reqwest, quick-xml, base64, percent-encoding
share LAN / P2P library sharing server & client tiny_http, serde, serde_json (requires webdav)
replaygain ReplayGain EBU R128 loudness analysis ebur128
speedpitch WSOLA time-stretching & pitch shifting wsola
sleeptimer Sleep timer (planned — see below) none
transcode On-the-fly Opus transcode of shared streams (future idea — see below) rubato, ogg-rs (optional)

Default features: ["webdav", "share", "replaygain", "speedpitch"]

When a feature is excluded at compile time, lightweight fallback stubs are provided so the Kotlin UI compiles seamlessly against the full UniFFI contract, while the compiled .so drops the heavy dependencies.

Minimal Build (No Default Features)

To build a lightweight APK containing only the local audio player engine:

./build.sh --no-default-features

Or assemble a release APK without installing to a device:

# 1. Build host library and generate UniFFI bindings
cargo build --lib --release
cargo run --release --bin uniffi-bindgen generate \
    --library target/release/librs_music_player.so \
    --language kotlin \
    --no-format \
    --out-dir android/app/src/main/java

# 2. Cross-compile Rust core for Android without default features
export ANDROID_NDK_HOME=/opt/android-ndk
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_PATH=$(pwd)/native/opus/aarch64-linux-android/lib/pkgconfig
export ANDROID_ABI=arm64-v8a

cargo ndk -t arm64-v8a -o android/app/src/main/jniLibs build --release --no-default-features

# 3. Assemble Release APK
cd android
./gradlew assembleRelease
# Output: android/app/build/outputs/apk/release/app-arm64-v8a-release.apk

Custom Feature Build

Select only the features you need (e.g. only ReplayGain + Speed/Pitch):

./build.sh --no-default-features --features "replaygain,speedpitch"

Planned: Sleep Timer (sleeptimer)

Optional end-of-evening timer, shipped as a compile-time module so it can be excluded from minimal builds like the other features (UI hides itself when the module is absent).

  • Countdown trigger — either after N tracks or after N minutes, N ∈ 1…180 (mode + value chosen in Settings).
  • End action (independently configurable in Settings):
    • Pause — pause the current track, keeping its position and the queue intact;
    • Close app — stop playback and shut the application down.

Future idea: On-the-fly transcoding (transcode)

Server-side transcode of shared-library streams (share://): decode → resample → re-encode, so weak links stream lossy instead of pulling full FLAC.

  • Scope — exactly two transports: share.rs / shared:// streams (both ends are this app, we control the protocol) and JellyFin (transcoding happens server-side via its API). WebDAV hosts (SFTPGo & co.) serve raw bytes and cannot transcode; other media servers (Plex, Subsonic/Navidrome, …) are not planned.
  • Why it's nearly free — the decode side (symphonia) and the encode side (libopus doubles as an encoder via audiopus) are already linked into the .so. Missing pieces: resampling 44.1k→48k (rubato, ~0.2 MB), optionally an Ogg muxer (ogg-rs, ~0.1 MB) for standard-player compatibility; ~200–400 lines of glue. Total: well under 1 MB, no new heavy dependencies.
  • Pipeline — decode → 48 kHz → Opus VBR 96–128 kbps → stream; encode runs magnitudes faster than realtime, so the server never becomes the bottleneck.
  • Rejected alternative — bundling ffmpeg: +25–50 MB per ABI for codecs we don't need; only worth revisiting if AAC/HLS is ever required.
  • Client-side transcoding is intentionally out of scope: the client decodes everything locally anyway, so re-encoding before decoding wastes battery and saves no bandwidth.

Repository layout

src/                    Rust core (compiled as cdylib for Android, rlib for desktop)
├── lib.rs              Module wiring + UniFFI re-exports (the FFI surface)
├── player/
│   ├── mod.rs          Player API: queue, playback state, DB access
│   └── output.rs       Audio engine: StreamControl, producer/consumer threads
├── stream.rs           Decoder abstraction (symphonia + audiopus/Opus), seek, duration
├── library/
│   ├── mod.rs          SQLite schema, scanner, queries, playlists, thumbnails
│   ├── metadata.rs     Tag reading (lofty) + fast duration probing
│   ├── webdav_scan.rs  WebDAV library scan (range-based metadata)
│   ├── loudness.rs     ReplayGain analysis (EBU R128)
│   └── lrc.rs          Synchronized & plain LRC parsing, formatting, and saving
├── webdav.rs           WebDAV client (PROPFIND, streaming, range fetch)
├── share.rs            P2P shared-library server + client sync
├── android_audio.rs    AudioTrack JNI bridge (Android only)
└── bin/uniffi-bindgen.rs   Kotlin binding generator (used by build.sh)

desktop/                Desktop application (Tauri 2 + SvelteKit + Tailwind v4)
├── src/                Frontend UI (Svelte 5 runes, TypeScript, Lucide icons)
│   ├── lib/            Components (FullscreenPlayer, LrcEditor, PlayerBar, Sidebar, etc.)
│   └── routes/         Main views (Tracks, Share, Settings, FolderExplorer)
└── src-tauri/          Tauri 2 backend (Rust), integrates rs-music-player core

android/                Android application (Kotlin, Jetpack Compose)
└── app/src/main/java/com/olyx/rsmusicplayer/
    ├── MainActivity.kt / SettingsScreen.kt / SharedLibraryScreen.kt
    ├── PlayerScreen.kt / FullScreenPlayer.kt / MiniPlayer.kt / Components.kt
    ├── PlayerViewModel.kt / PlayerRepository.kt / PlayerHolder.kt
    ├── PlaybackService.kt / AudioFocusHelper.kt / NsdHelper.kt
    └── ../uniffi/…/rs_music_player.kt   generated bindings — do not edit

scripts/                Build, packaging and benchmark scripts
├── build-linux.sh      Build Linux desktop binary and package bundles
├── build-windows.sh    Cross-compile Windows desktop .exe via cargo-xwin
└── profile_on_device.sh Profiling tool for Android devices

tests/                  Rust integration tests (real asserts, no local-media dependencies)
native/opus/            Prebuilt libopus for Android (ARM64, ARMv7) and Windows (MSVC)
.cargo/config.toml      Linker configurations
build.sh                Android build pipeline script
TODO.md                 Live roadmap checklist

Architecture notes

  • Heavy logic (decode, SQLite, DSP, HTTP, LRC parsing) lives in Rust; UI layers are thin.
  • Audio path: decoder thread → lock-free ring buffer → output (cpal on desktop, AudioTrack/JNI on Android). Volume, speed/pitch (WSOLA), ReplayGain are applied in Rust before the ring buffer.
  • Gapless advance (mpv-style): EOF track switching is decided by the audio thread at buffer drain — not by UI timers — so suspend can't stall it and no boundary wake lock is needed. The same audio output is kept open across same-format tracks; a format change swaps in a fresh one.