2026-03-28 13:49:47 -07:00
# NavidromePlayer
A native iOS + watchOS music player for [Navidrome ](https://www.navidrome.org/ ) servers, built with SwiftUI and AVFoundation. Features a Mitsuha-style audio visualizer, Apple Watch companion app, radio streaming with Shazam identification, and an optional Companion API for advanced server-side features.
## Features
### iOS App
- **Full Navidrome/Subsonic library** — browse by albums, artists, songs, genres, playlists
2026-04-10 07:00:30 -07:00
- **Offline playback** — download songs for offline listening with per-track and per-album downloads
- **Mitsuha visualizer** — real-time FFT waveform visualization with Catmull-Rom spline rendering, multiple styles (wave, bar, line), color modes (dynamic, album art, custom), and configurable presets
2026-03-28 13:49:47 -07:00
- **Now Playing** — full-screen player with album art color extraction, drag-to-dismiss, AirPlay, seek bar, queue management
- **Mini player** — persistent bottom bar with scrubbable progress, visualizer overlay, transport controls
2026-04-10 07:00:30 -07:00
- **Radio streaming** — live radio with HLS/PLS/M3U playlist resolution, timeshift buffering, recording, and Shazam identification
- **Smart DJ** — crossfade engine with silence skipping, loudness normalization (LUFS), and predictive transitions (requires Companion API)
2026-03-28 13:49:47 -07:00
- **Batch metadata editing** — edit album/artist tags across multiple albums simultaneously (requires Companion API)
- **Custom album covers** — long-press album art to replace with photos from your library
- **Multi-server support** — automatic failover between servers with the same credentials
- **Background audio** — Lock Screen / Control Center controls, Dynamic Island support
2026-04-10 07:00:30 -07:00
- **Keyboard dismiss** — tap anywhere outside text fields or scroll to dismiss
2026-03-28 13:49:47 -07:00
### watchOS App
- **Offline playback** — transfer songs from iPhone to Apple Watch for standalone listening
- **Bluetooth + Speaker mode** — Apple Watch Ultra speaker support via HKWorkoutSession
- **Crown control** — Digital Crown volume control with haptic feedback
- **Compact visualizer** — waveform visualization on watch face
### Companion API (Optional)
A Python FastAPI server that runs alongside Navidrome on your server (e.g., Raspberry Pi) providing:
- **Smart DJ analysis** — BPM detection, silence boundary mapping, LUFS loudness measurement
- **Mitsuha visualizer pre-computation** — generate FFT frames on the server instead of on-device
- **Remote ID3 tag editing** — edit metadata on server files via mutagen
2026-04-10 07:00:30 -07:00
- **File uploads** — upload and auto-tag new music via the app
- **WebSocket push** — real-time notifications to the iOS app when metadata changes or uploads complete
2026-03-28 13:49:47 -07:00
- **Navidrome scan trigger** — automatically rescan after tag edits
## Architecture
```
NavidromePlayer/
├── Shared/ # Code shared between iOS and watchOS
│ ├── API/SubsonicClient.swift # Full Subsonic/Navidrome REST client
│ ├── Audio/AudioPlayer.swift # AVPlayer + AVAudioEngine, FFT, visualizer levels
│ ├── Models/Models.swift # Codable models for all API responses
│ └── Storage/
2026-04-10 07:00:30 -07:00
│ ├── LibraryCache.swift # Disk cache for instant offline browsing
2026-03-28 13:49:47 -07:00
│ ├── OfflineManager.swift # Download manager with progress tracking
│ ├── ServerManager.swift # Multi-server with auto-failover
│ └── WatchConnectivityManager.swift # WCSession file transfers
│
├── iOS/
2026-04-10 07:00:30 -07:00
│ ├── App/NavidromePlayerApp.swift # Entry point, background upload delegate
2026-03-28 13:49:47 -07:00
│ └── Views/
2026-04-10 07:00:30 -07:00
│ ├── Common/ # MainTabView, MiniPlayer, AsyncCoverArt, DebugConsole
2026-03-28 13:49:47 -07:00
│ ├── Companion/ # Companion API integration
│ │ ├── CompanionAPIService.swift # API client + WebSocket push client
│ │ ├── CompanionSettingsView.swift # Config, Smart DJ toggles, analysis triggers
│ │ ├── SmartCrossfadeManager.swift # Dual AVPlayer A/B crossfade engine
│ │ ├── TrackEditorView.swift # Single-track metadata editor
│ │ ├── BatchAlbumEditorSheet.swift # Edit tags for one album
│ │ ├── MultiAlbumEditorSheet.swift # Batch edit across multiple albums
│ │ ├── BatchUploadView.swift # Zip import + batch upload
2026-04-10 07:00:30 -07:00
│ │ └── ZipImportManager.swift # Background URLSession uploads
2026-03-28 13:49:47 -07:00
│ ├── Library/ # MyMusic, Albums, Artists, Playlists, Search, Radio, Downloads
│ ├── Login/ # Server configuration
│ ├── NowPlaying/ # Full player, SiriSeekBar, RadioStreamBuffer, Shazam
2026-04-10 07:00:30 -07:00
│ └── Visualizer/ # MitsuhaVisualizerView, OfflineAudioAnalyzer, storage
2026-03-28 13:49:47 -07:00
│
├── watchOS/
│ ├── App/ # Watch app entry, offline store, session manager
│ ├── Audio/WatchAudioPlayer.swift # Dual mode: Bluetooth + Ultra speaker
2026-04-10 07:00:30 -07:00
│ └── Views/ # Library, NowPlaying, Setup, Visualizer
2026-03-28 13:49:47 -07:00
│
2026-04-10 07:00:30 -07:00
├── project.yml # XcodeGen project definition
2026-03-28 13:49:47 -07:00
└── generate.sh # Regenerate .xcodeproj
```
## Requirements
2026-04-10 07:00:30 -07:00
- **iOS 17.0+** / **watchOS 10.0+**
- **Xcode 15+**
2026-03-28 13:49:47 -07:00
- **XcodeGen** — `brew install xcodegen`
- **Navidrome server** — any version with Subsonic API support
## Building
```bash
./generate.sh
# Opens Xcode automatically. Select device and build.
```
Set your Apple Developer Team ID in `project.yml` under `DEVELOPMENT_TEAM` .
2026-04-10 07:00:30 -07:00
---
2026-03-28 13:49:47 -07:00
## Companion API
The Companion API is optional. Without it, the app works as a standard Navidrome player. With it, you get Smart DJ, tag editing, uploads, and server-side visualizer pre-computation.
### Server Directory Structure
```
/home/pi/docker/navidrome/
├── docker-compose.yml # Both Navidrome + Companion
2026-04-10 07:00:30 -07:00
├── navidrome_data/ # Navidrome database (auto-created)
├── companion_data/ # Persistent data (auto-created)
│ ├── smart_dj.db # BPM, silence, loudness profiles
│ └── vis_cache/ # Pre-computed Mitsuha FFT frames
└── companion_api/ # Companion API source code
2026-03-28 13:49:47 -07:00
├── Dockerfile
├── main.py
└── pre_analyze.py
2026-04-10 07:00:30 -07:00
/home/pi/navidrome/music/ # Your music library
2026-03-28 13:49:47 -07:00
├── Artist/Album/song.flac
└── ...
```
### docker-compose.yml
```yaml
services:
navidrome:
image: deluan/navidrome:latest
container_name: navidrome
restart: unless-stopped
ports:
- "4533:4533"
environment:
- ND_SCANSCHEDULE=1h
- ND_BASEURL=/navidrome
volumes:
- /home/pi/navidrome:/music:ro
- ./navidrome_data:/data
music-companion:
build: ./companion_api
container_name: music-companion
restart: unless-stopped
ports:
- "8000:8000"
volumes:
- /home/pi/navidrome/music:/music:rw
- ./companion_data:/app/data
environment:
- MUSIC_DIR=/music
- DB_PATH=/app/data/smart_dj.db
- VIS_CACHE_DIR=/app/data/vis_cache
- NAVIDROME_URL=http://navidrome:4533/navidrome
- SUBSONIC_USER=your_username
- SUBSONIC_TOKEN=your_token
- SUBSONIC_SALT=your_salt
depends_on:
- navidrome
```
> **Volume mount note:** The companion mounts `/home/pi/navidrome/music` directly (not the parent `/home/pi/navidrome`). This ensures `MUSIC_DIR=/music` maps to your actual music files without path prefix issues.
### Commands
```bash
cd /home/pi/docker/navidrome
# ── Startup ──────────────────────────────────────────────
docker compose up -d # Start everything
docker compose up -d --build music-companion # Rebuild after code changes
docker compose logs -f music-companion # View logs
# ── Health Check ─────────────────────────────────────────
curl http://localhost:8000/health
# Returns: profiles count, vis cache count, connected clients
# ── Pre-Analysis (run after adding new music) ────────────
docker compose exec music-companion python pre_analyze.py # Analyze missing (DJ + vis)
docker compose exec music-companion python pre_analyze.py --dj # DJ profiles only (faster)
docker compose exec music-companion python pre_analyze.py --vis # Visualizer frames only
docker compose exec music-companion python pre_analyze.py --force # Re-analyze everything
2026-04-10 07:00:30 -07:00
# ── Reset Analysis Data ─────────────────────────────────
2026-03-28 13:49:47 -07:00
rm companion_data/smart_dj.db # Delete DJ profiles
rm -rf companion_data/vis_cache/* # Delete vis frame cache
docker compose exec music-companion python pre_analyze.py # Regenerate from scratch
# ── Maintenance ──────────────────────────────────────────
docker compose restart music-companion # Restart API
docker compose down # Stop everything
docker compose down -v # Stop + remove volumes
```
### API Endpoints
2026-04-10 07:00:30 -07:00
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/health` | Server status, profile count, vis cache count, connected clients |
| `PATCH` | `/edit-metadata` | Edit ID3 tags (JSON body: `relative_path` , `title` , `artist` , `album` , etc.) |
| `POST` | `/upload-track` | Upload audio file with metadata (multipart: `file` , `title` , `artist` , `album` ) |
| `GET` | `/smart-dj/profile?relative_path=...` | BPM, silence start/end, LUFS for a track |
| `GET` | `/smart-dj/bulk-profiles?paths=...` | Batch fetch profiles (comma-separated paths) |
| `GET` | `/visualizer/frames?relative_path=...` | Pre-computed Mitsuha FFT frames (JSON array) |
| `POST` | `/visualizer/precompute` | Trigger background vis frame generation for all tracks |
| `POST` | `/bulk-fix` | Trigger Navidrome library rescan |
| `WS` | `/ws/push` | Real-time push events to iOS app |
2026-03-28 13:49:47 -07:00
### Path Resolution
2026-04-10 07:00:30 -07:00
Navidrome's `song.path` field can differ from the actual filesystem path. The Companion API uses a three-strategy resolver:
2026-03-28 13:49:47 -07:00
1. **Direct join** — `MUSIC_DIR + relative_path`
2026-04-10 07:00:30 -07:00
2. **Strip prefix** — removes leading components one at a time (handles Navidrome's library folder prefix)
3. **Filename search** — walks the music directory looking for an exact filename match
2026-03-28 13:49:47 -07:00
If a 404 still occurs, the error response includes the exact paths that were tried for debugging.
### iOS App Configuration
1. **Settings** → **Companion API**
2026-04-10 07:00:30 -07:00
2. Enter your server's IP address and port (default: 8000)
3. Toggle **Enable Companion API**
4. Tap **Test Connection** to verify
5. Toggle **Smart DJ** for crossfade and analysis features
2026-03-28 13:49:47 -07:00
2026-04-10 07:00:30 -07:00
---
2026-03-28 13:49:47 -07:00
## Feature Details
### Mitsuha Visualizer
The visualizer renders smooth liquid waveforms using Catmull-Rom splines with configurable tension. FFT data comes from one of three sources:
- **Real-time engine FFT** — AVAudioEngine tap on local files
- **Offline pre-analyzed frames** — cached FFT data synced to playback position
- **Server-computed frames** — downloaded from Companion API (saves device battery)
2026-04-10 07:00:30 -07:00
Settings include per-view configuration for Now Playing and Mini Player independently, with four built-in presets. The mini player visualizer automatically pauses when the full-screen Now Playing view is open (battery optimization).
2026-03-28 13:49:47 -07:00
### Radio
- **Playlist resolution** — resolves `.pls` , `.m3u` , `.asx` playlist URLs to direct stream URLs
- **HLS detection** — identifies HLS streams from URL extension or content-type, disables raw buffering
- **Timeshift** — buffer live radio and scrub back through recent audio
- **Recording** — capture radio segments to local files
- **Shazam** — identify currently playing content via SHSession + dedicated AVAudioEngine mic tap
- **Recorded playback** — recorded radio files play with ±5s skip buttons instead of prev/next
2026-04-10 07:00:30 -07:00
- **Live toggle** — start/stop buffering, snap to live edge
2026-03-28 13:49:47 -07:00
### Batch Tag Editing
Long-press any album to:
- **Edit Album** — edit tags for all tracks in that album
2026-04-10 07:00:30 -07:00
- **Select Albums** — enter multi-select mode (nav bar transforms: Cancel / count / Edit button), tap albums to select, "Select All" toggle, then apply changes across all selected albums
2026-03-28 13:49:47 -07:00
2026-04-10 07:00:30 -07:00
The "Set same artist on all tracks" toggle fixes compilation albums that split into separate per-artist entries in Navidrome.
2026-03-28 13:49:47 -07:00
### Downloads
Split into two sub-tabs:
- **Offline** — storage usage, downloaded songs with playback, swipe to delete
- **Watch** — Apple Watch connection status, pending transfers with progress, songs on watch, send all / delete all
### Debug Console
Toggle in Settings → Developer. Two display modes:
- **Docked** — panel above the tab bar, drag handle to resize (120– 500pt)
- **PiP** — floating draggable/resizable window with collapse, dock-back, and close buttons
### Caching
All views use cache-first loading to eliminate spinners on warm launches:
2026-04-10 07:00:30 -07:00
- **LibraryCache** — albums, artists, playlists, genres, album/artist details as JSON
- **ImageCache** — two-tier NSCache + disk JPEG with 200MB limit and LRU eviction
2026-03-28 13:49:47 -07:00
- **SmartDJCache** — Smart DJ profiles cached locally per song path
- **AlbumCoverStore** — user-set custom album covers in Documents
- **VisualizerStorageManager** — pre-analyzed FFT frames per song
Detail views load from cache instantly, refresh from server silently, and show a Retry button if both cache and server are unavailable.
2026-04-10 07:00:30 -07:00
---
2026-03-28 13:49:47 -07:00
## License
2026-04-10 07:00:30 -07:00
Personal project. Not affiliated with Navidrome.