Note: this does not actually seem to populate the recently played list, so it's kind of useless still. Sends playback_session events to Tidal when tracks finish playing. Events are triggered on track change, user stop, and app exit. Controlled by tidalPlaybackEventReportingEnabled setting. - Add TidalPlaybackEventTracker to observe playback state - Add sourceType/sourceId to TidalTrackMetadata for context - Populate source context in Album/Playlist/Mix ViewModels - Start tracker on app init, finalize on app exit 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 | ||
| plan.txt | ||
| README.md | ||
| settings.gradle.kts | ||
| shell.nix | ||
| TIDAL_API_REFERENCE.md | ||
| TIDAL_EVENT_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
- Tidal "recently played" is not populated. The "Enable playback event reporting" option was supposed to fix that but it doesn't work.
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)
Unit Tests
./gradlew test # Run JVM unit tests
./gradlew connectedAndroidTest # Run instrumented tests (device required)
Unit tests cover metadata extraction converters, storage location parsing, and in-flight deduplication. Instrumented tests verify FFmpeg extraction with real audio files.
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.