An android music player with bit-perfect USB output and native (no electron!) Tidal integration
IntentAudioFileBuilder was creating SMB AudioFiles with size=0 and lastModified=0, causing metadata cache validation to always fail and trigger extraction even when data was already cached. Now fetches file stats from metadata cache when building SMB AudioFiles from canonical paths. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| app | ||
| build-ffmpeg | ||
| dependencies | ||
| gradle/wrapper | ||
| scripts | ||
| .gitignore | ||
| .gitmodules | ||
| build.gradle.kts | ||
| CLAUDE.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle.kts | ||
| shell.nix | ||
| TIDAL_API_REFERENCE.md | ||
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 APKemulator.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 emulatorapp_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_AUDIOintent 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.