An android music player with bit-perfect USB output and native (no electron!) Tidal integration
Find a file
Davíð Steinn Geirsson 1ca2e4af41 Fix foreground service crash without breaking media controls
The previous fix (50a8c69) called startForeground() in onStartCommand() but
this broke media controls because Media3's MediaNotificationManager internally
calls startForegroundService() when updating notifications, which re-triggers
onStartCommand() and overwrites Media3's notification with our placeholder.

Fix by tracking whether we've already satisfied the foreground requirement
with a foregroundStarted flag. Only the first onStartCommand() call shows
the placeholder; subsequent calls skip it so Media3's notification with
media controls is preserved.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 21:02:56 +00:00
app Fix foreground service crash without breaking media controls 2026-01-14 21:02:56 +00:00
build-ffmpeg Replace Amplituda with custom seekable FFmpeg waveform extractor 2026-01-09 20:31:52 +00:00
dependencies Update androidx-media: fix slow Ogg seeking with seek map 2026-01-13 20:24:03 +00:00
gradle/wrapper Add gradle wrapper 2026-01-02 14:33:49 +00:00
scripts app_restart.sh: Allow downgrade (for bisecting) 2026-01-13 17:35:03 +00:00
.gitignore Add release build signing and configuration 2026-01-10 03:31:47 +00:00
.gitmodules Modernize Tidal API client, remove submodule 2026-01-11 07:06:58 +00:00
build.gradle.kts Add release build signing and configuration 2026-01-10 03:31:47 +00:00
CLAUDE.md Add SMB read-ahead buffer for prefetching 2026-01-14 17:38:37 +00:00
gradle.properties Use local build of androidx-media and ffmpeg 2026-01-05 17:06:35 +00:00
gradlew Add gradle wrapper 2026-01-02 14:33:49 +00:00
gradlew.bat Add gradle wrapper 2026-01-02 14:33:49 +00:00
README.md Add README.md 2026-01-12 22:19:54 +00:00
settings.gradle.kts Add Tidal track playback support 2026-01-11 08:29:12 +00:00
shell.nix Misc UI polish 2026-01-04 17:19:56 +00:00
TIDAL_API_REFERENCE.md Add Tidal API reference (generated from python-tidal source) 2026-01-11 08:31:08 +00:00

PlaceboPlayer

A minimalist Android music player focused on audio quality for USB DACs.

Key Features

  • Bit-perfect audio output - Direct USB DAC support with no resampling or mixing
  • Gapless playback - Seamless transitions between tracks
  • Folder-based navigation - No library, just browse your files
  • SMB network storage - Access music on network shares (NAS, Windows shares, Samba)
  • Privacy-first - No tracking, no analytics (network only used for SMB)
  • AOSP compatible - Works on pure Android without Google services
  • Tidal streaming - In-app streaming through Tidal

Technical Highlights

  • Min SDK: API 34 (Android 14.0+) for advanced AudioTrack API
  • Architecture: MVVM with Jetpack Compose
  • Playback: Media3 fork with BitPerfectAudioOutputProvider for USB DACs
  • Media Session: Lock screen controls and notification integration
  • Data Architecture: Dual database design (persistent settings + regenerable cache)
    • Persistent Database (userdata): Settings, playlists, playback history
    • Cache Database (cache storage): Metadata, cover art (cleared by Android when storage low)

Known issues

  • The buffer usage reporting seems wrong

Planned features

  • Smarter buffering implementation (for now we're only using ExoPlayer's built in player cache)

Quick Start

# Build debug APK
./gradlew assembleDebug

# Build release APK (requires keystore.properties)
./scripts/build_release.sh

# Start emulator with GUI, install the built apk and run it
./scripts/emulator.sh --gui

Release Builds

Create keystore.properties in the project root:

storeFile=/path/to/your/keystore.p12
storePassword=your_password
keyAlias=your_alias
keyPassword=your_password

Available Scripts

  • build_release.sh - Build signed release APK
  • emulator.sh - Install APK and run app in emulator, starting the emulator if not already running.
    • --gui - Enable graphical interface
    • --install-apk <path> - Install specified APK instead of the default
  • app_restart.sh - Kill, reinstall and restart the app on emulator
  • app_logcat.sh - Stream logs from the app (use with timeout)
  • app_play_intent.sh - Trigger playback via intent (for automated testing)

Automated Testing & Debugging

The app supports intent-based playback triggering for automated testing:

# Trigger playback with test files
./scripts/app_play_intent.sh

# View logs (use timeout to limit output)
timeout 10 ./scripts/app_logcat.sh

# Reinstall and restart app
./scripts/app_restart.sh

The app_play_intent.sh script demonstrates how to:

  • Send a PLAY_AUDIO intent with a content:// URI
  • Provide an optional queue of tracks
  • Trigger playback programmatically without UI interaction

See CLAUDE.md for technical details.

Documentation

  • CLAUDE.md - Technical documentation for developers and AI agents

License

GPLv2+

Waveform extraction and display code is originally based on Andrii Serbeniuk's Amplituda and compose-audiowaveform code, which is licensed under the Apache 2.0 license.

The Tidal client functionality is originally based on https://github.com/0xf4b1/tidal-kt with heavy modifications. That code is licensed under GPL3.

For ffmpeg and androidx-media, see the LICENSE files in the respective submodule.