Music Control (Web Remote for Denon/HEOS)¶
Web-based remote control for Denon AVR with HEOS integration and Navidrome streaming.
Overview¶
┌─────────────────────────────────────────────────────────────┐
│ BROWSER / MOBILE │
│ http://10.89.97.100:3002 │
└─────────────────┬───────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MUSIC-CONTROL │
│ Next.js App (Proxmox) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Denon API │ │ HEOS API │ │ Navidrome │ │
│ │ (Telnet) │ │ (TCP) │ │ (Subsonic) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
└─────────┼────────────────┼────────────────┼────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Denon AVR │ │ HEOS Player │ │ Navidrome │
│ X1700H │ │ (same unit) │ │ (arr-stack) │
│ 10.89.97.15 │ │ Port 1255 │ │ Port 4533 │
└──────────────┘ └──────────────┘ └──────────────┘
Features¶
Denon AVR Control¶
- Power on/off (main + Zone 2)
- Volume control with slider
- Mute toggle
- Input selection (Apple TV, TV Audio, Cable, Blu-ray, Tuner)
HEOS Music Playback¶
- Play/Pause/Stop controls
- Stream from Navidrome playlists
- Play random songs from library
- Now playing display with album art
Navidrome Integration¶
- Browse and play playlists
- Random song shuffle
- Scrobbling to track listening history
- Album art display
Network¶
| Service | Address | Protocol |
|---|---|---|
| Music Control UI | http://10.89.97.100:3002 |
HTTP |
| Denon AVR | 10.89.97.15:23 |
Telnet |
| HEOS | 10.89.97.15:1255 |
TCP/JSON |
| Navidrome | 10.89.97.50:4533 |
HTTP/Subsonic |
Tech Stack¶
- Framework: Next.js 15 + React 19
- Styling: Tailwind CSS v4 + shadcn/ui
- APIs:
- Denon AVR: Telnet protocol
- HEOS: TCP socket with JSON responses
- Navidrome: Subsonic API
File Structure¶
/root/projects/music-control/
├── src/
│ ├── app/
│ │ ├── page.tsx # Main UI
│ │ └── api/
│ │ ├── denon/ # Denon telnet commands
│ │ ├── heos/ # HEOS TCP commands
│ │ └── navidrome/ # Subsonic API proxy
│ │ ├── route.ts # Now playing, playlists
│ │ ├── random/ # Random songs
│ │ ├── playlist/[id]/ # Playlist details
│ │ ├── song/[id]/ # Song details
│ │ └── scrobble/ # Scrobble endpoint
│ └── lib/
│ ├── denon.ts # Denon telnet client
│ ├── heos.ts # HEOS TCP client
│ └── navidrome.ts # Subsonic API client
├── .env.local # Configuration
└── package.json
Configuration¶
# /root/projects/music-control/.env.local
DENON_HOST=10.89.97.15
NAVIDROME_URL=http://10.89.97.50:4533
NAVIDROME_USER=your-username
NAVIDROME_PASS=your-password
Running¶
Development¶
Production (tmux)¶
tmux new-session -d -s music-control 'cd /root/projects/music-control && npm run dev'
tmux attach -t music-control
API Reference¶
Denon Control¶
# Get status
curl http://10.89.97.100:3002/api/denon
# Send command
curl -X POST http://10.89.97.100:3002/api/denon \
-H "Content-Type: application/json" \
-d '{"command": "PWON"}' # Power on
# Common commands:
# PWON/PWSTANDBY - Power
# MVUP/MVDOWN - Volume
# MUON/MUOFF - Mute
# SIMPLAY/SITV/SISAT - Input select
HEOS Control¶
# Get player status
curl http://10.89.97.100:3002/api/heos
# Play/Pause/Stop
curl -X POST http://10.89.97.100:3002/api/heos \
-H "Content-Type: application/json" \
-d '{"action": "play"}'
Navidrome¶
# Get now playing + playlists
curl http://10.89.97.100:3002/api/navidrome
# Get random songs
curl http://10.89.97.100:3002/api/navidrome/random
# Play a playlist
curl -X POST http://10.89.97.100:3002/api/navidrome/playlist/PLAYLIST_ID \
-H "Content-Type: application/json" \
-d '{"action": "play"}'
# Scrobble a song
curl -X POST http://10.89.97.100:3002/api/navidrome/scrobble \
-H "Content-Type: application/json" \
-d '{"id": "SONG_ID", "submission": false}'
HEOS Protocol¶
HEOS uses a simple TCP protocol on port 1255:
# Send command
printf 'heos://player/get_players\r\n' | nc 10.89.97.15 1255
# Response (JSON)
{
"heos": {"command": "player/get_players", "result": "success"},
"payload": [{"name": "Denon AVR-X1700H", "pid": 1415590613, ...}]
}
Key HEOS Commands¶
| Command | Description |
|---|---|
player/get_players |
List available players |
player/get_play_state?pid=X |
Get play/pause/stop state |
player/get_now_playing_media?pid=X |
Current track info |
player/set_play_state?pid=X&state=play |
Play |
player/set_play_state?pid=X&state=pause |
Pause |
browse/play_stream?pid=X&url=URL |
Play stream URL |
Scrobbling Behavior¶
Music-control sends scrobbles to Navidrome every 30 seconds while playing. This updates the "now playing" status that other apps (like music-display) can query.
Known Issue: Multiple browser tabs with music-control open can cause duplicate scrobbles, leading to flip-flopping "now playing" data.
Solution: Use HEOS as the authoritative source for now-playing data instead of Navidrome scrobbles. See HEOS Source Plan.
Troubleshooting¶
Denon Not Responding¶
HEOS Connection Failed¶
# Test HEOS port
nc -zv 10.89.97.15 1255
# Try getting players directly
printf 'heos://player/get_players\r\n' | timeout 5 nc 10.89.97.15 1255
Volume Not Changing¶
The AVR may be in a mode that blocks volume changes (e.g., Audyssey dynamic volume). Check receiver settings.
Related Documentation¶
- Music Display - LED matrix display
- Music Stack - Navidrome server
- HEOS Source Plan - Planned HEOS integration