An android music player with bit-perfect USB output and native (no electron!) Tidal integration
Find a file
Davíð Steinn Geirsson 9fbe0fe92e Fix SMB share-closed error during credit exhaustion retry
When credit exhaustion occurred, the retry logic reused the same DiskShare
object which could become invalid during the retry delay, causing
"DiskShare has already been closed" errors.

Now the share is re-acquired on each retry attempt. Added withShareRetry()
functions that manage share lifecycle within the retry loop, and updated
all SMB operations to use them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 00:38:08 +00:00
app Fix SMB share-closed error during credit exhaustion retry 2026-01-25 00:38:08 +00:00
build-ffmpeg Replace Amplituda with custom seekable FFmpeg waveform extractor 2026-01-09 20:31:52 +00:00
dependencies Fix build deprecation warnings 2026-01-24 23:48:17 +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 Rename TidalBrowser* to TidalHome* for clarity 2026-01-24 12:48:38 +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
plan.txt Fix genre pages using page-based API 2026-01-24 12:39:22 +00:00
README.md Integrate Tidal playback event reporting into playback engine 2026-01-24 16:14:08 +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
TIDAL_EVENT_API_REFERENCE.md Add Tidal event API reference (llm generated from official SDKs) 2026-01-24 15:06:52 +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

  • 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 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)

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_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.