Skip to content

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

cd /root/projects/music-control
npm run dev
# Runs on http://localhost:3002

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"}'
# 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

# Test telnet connection
nc -zv 10.89.97.15 23

# Check if receiver is on network
ping 10.89.97.15

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.