Files
media-player-server/README.md
T
alexei.dolgolyov ddf4a6cb29
Lint & Test / test (push) Successful in 20s
Lint & Test / linux-smoke (push) Failing after 34s
feat: production-ready Linux & macOS support
- Add `linux` (dbus-python, PyGObject, python-xlib) and `macos`
  (pyobjc) extras to pyproject.toml with sys_platform markers; move
  cross-platform screen-brightness-control + monitorcontrol to base deps.
- build-dist-linux.sh: install `.[linux]`, pkg-config pre-flight for
  dbus-1/glib-2.0, emit a systemd unit with DBUS_SESSION_BUS_ADDRESS +
  XDG_RUNTIME_DIR + ReadWritePaths for ~/.config and ~/.cache so MPRIS
  works and audit-log / thumbnail writes aren't blocked by ProtectHome.
- New build-dist-macos.sh + per-user LaunchAgent installer producing
  MediaServer-vX.Y-macos-{arm64,x86_64}.tar.gz.
- Templated media-server.service updated to match the dist layout with
  proper session-bus env vars and a writable state-dir grant.
- install_linux.sh: drop dead requirements.txt path; install via
  `pip install ".[linux]"` and pre-create the writable state dirs.
- Cross-platform album artwork: abstract MediaController.get_album_art()
  with Linux (mpris:artUrl, file:// + http(s)://) and macOS (Spotify URL)
  impls; routes/media artwork endpoint now awaits the controller.
- LinuxMediaController connects to the session bus lazily — failure no
  longer crashes lifespan startup; MPRIS calls return idle until the bus
  is reachable. Logged once at INFO with a hint about
  `loginctl enable-linger`.
- Startup preflight on Linux warns if DBUS_SESSION_BUS_ADDRESS or
  XDG_RUNTIME_DIR is unset and informs the user when Wayland disables
  the foreground probe.
- /api/media/visualizer/status now reports a per-OS unavailable_reason.
- tray._confirm guarded against ctypes.windll on non-Windows.
- config.example.yaml: per-OS commented script examples; on_turn_off
  default is now a no-op echo (used to silently fail off Windows).
- README: replace stale `pip install -r requirements.txt` instructions
  with the new extras; add systemd lingering doc + troubleshooting
  section; add macOS LaunchAgent section.
- CI: new linux-smoke job (installs `.[linux]`, boots the server under
  dbus-run-session, asserts /api/health). Release workflow gains
  apt-deps step for the Linux build and a best-effort macOS build job.
2026-05-26 12:17:30 +03:00

974 lines
33 KiB
Markdown

# Media Server
A REST API server for controlling system media playback on Windows, Linux, macOS, and Android.
## Features
- **Built-in Web UI** for real-time media control and monitoring
- **Installable PWA** - Add to home screen on mobile for a native app experience
- **Audio Visualizer** - Real-time spectrum analyzer with beat-reactive album art effects
- **Dynamic WebGL Background** - Audio-reactive animated background with album art color extraction
- **Media Browser** - Browse and play media files from configured folders
- **Display Control** - Monitor brightness and power management
- **Quick Actions & Scripts** - Execute custom scripts with one click
- **Callbacks** - Trigger commands on media events (play, pause, volume, etc.)
- **Header Links** - Configurable quick-access links in the UI header
- Control any media player via system-wide media transport controls
- Play/Pause/Stop/Next/Previous track
- Volume control and mute
- Seek within tracks
- Get current track info (title, artist, album, artwork)
- WebSocket support for real-time updates
- Token-based authentication with multi-token support
- Dark/light theme with customizable accent colors
- Multi-language support (English, Russian)
- Cross-platform support (Windows, Linux, macOS, Android)
## Web UI
The media server includes a built-in web interface for controlling and monitoring media playback.
![Web UI](docs/web-ui.PNG)
### Web UI Highlights
- **Real-time status updates** via WebSocket connection
- **Album artwork display** with glow effect and automatic updates
- **Vinyl record mode** - Album art displayed as a spinning vinyl disc with grooves and center spindle
- **Playback controls** - Play, pause, next, previous
- **Volume control** with mute toggle
- **Seekable progress bar** - Click to jump to any position
- **Mini player** - Sticky compact player that appears when scrolling away from the main player
- **Connection status indicator** - Know when you're connected
- **Token authentication** - Saved in browser localStorage
- **Audio spectrum visualizer** - Real-time frequency bars with beat-reactive album art scaling and glow (on-demand WASAPI loopback capture)
- **Dynamic WebGL background** - Fragment shader-based animated background that reacts to audio beats and extracts colors from album art (toggle on/off in header)
- **Display control** - Monitor brightness adjustment and power on/off
- **Header quick links** - Configurable external URLs with icons shown in the header bar
- **Installable PWA** - Add to home screen on mobile/desktop for standalone app experience with safe area support for notched phones
- **Responsive design** - Works on desktop, tablet, and mobile
- **Dark and light themes** - Toggle between dark and light modes with dynamic status bar theming
- **Accent color picker** - Choose from 9 preset accent colors or pick a custom color
- **Tab-based navigation** - Player, Display, Browser, Quick Actions, and Settings tabs
- **Multi-language support** - English and Russian locales with automatic detection
### Accessing the Web UI
1. Start the media server:
```bash
python -m media_server.main
```
2. Open your browser and navigate to:
```text
http://localhost:8765/
```
3. Enter your API token when prompted (get it with `media-server --show-token`)
4. Start playing media in any supported player and watch the UI update in real-time!
### Installing as a PWA
The Web UI can be installed as a Progressive Web App for a native app-like experience:
1. Open the Web UI in Chrome/Edge on your phone or desktop
2. Tap the **Install** icon in the address bar (or "Add to Home Screen" on mobile)
3. The app launches in standalone mode — no browser chrome, with proper safe area handling for notched phones
### Audio Visualizer
The Web UI includes a real-time audio spectrum visualizer that captures system audio output:
- **On-demand capture** - Audio capture starts only when a client enables the visualizer, and stops when the last client disconnects
- **Beat-reactive effects** - Album art pulses and glows in response to bass frequencies
- **Dynamic WebGL background** - Animated shader background that reacts to bass frequencies and adapts colors from current album art
- **Configurable device** - Select which audio output device to capture in Settings
Requires `soundcard` and `numpy` Python packages. Enable in `config.yaml`:
```yaml
visualizer_enabled: true
visualizer_fps: 30 # Frame rate (10-60)
visualizer_bins: 32 # Frequency bins (8-128)
# visualizer_device: "Speakers" # optional: specific device name
```
### Remote Access
To access the Web UI from other devices on your network:
1. Find your computer's IP address (e.g., `192.168.1.100`)
2. Navigate to `http://192.168.1.100:8765/` from any device on the same network
3. Enter your API token
**Security Note:** For remote access over the internet, use a reverse proxy with HTTPS (nginx, Caddy) to encrypt traffic.
### Localization
The Web UI supports multiple languages with automatic browser locale detection:
**Available Languages:**
- **English (en)** - Default
- **Русский (ru)** - Russian
The interface automatically detects your browser language on first visit. You can manually switch languages using the dropdown in the top-right corner of the Web UI.
**Contributing New Locales:**
We welcome translations for additional languages! To contribute a new locale:
1. Copy `media_server/static/locales/en.json` to a new file named with your language code (e.g., `de.json` for German)
2. Translate all strings to your language, keeping the same JSON structure
3. Add your language to the `supportedLocales` object in `media_server/static/index.html`:
```javascript
const supportedLocales = {
'en': 'English',
'ru': 'Русский',
'de': 'Deutsch' // Add your language here
};
```
4. Test the translation by switching to your language in the Web UI
5. Submit a pull request with your changes
See [CLAUDE.md](CLAUDE.md#internationalization-i18n) for detailed translation guidelines.
## Media Browser
The Media Browser feature allows you to browse and play media files from configured folders directly through the Web UI.
![Media Browser](docs/media-browser.PNG)
### Browser Highlights
- **Folder Configuration** - Mount multiple media folders (music/video directories)
- **Recursive Navigation** - Browse through folder hierarchies with breadcrumb navigation
- **Multiple View Modes** - Grid, compact grid, and list views with toggle buttons
- **Thumbnail Display** - Automatically generated thumbnails from album art (lazy-loaded)
- **Metadata Extraction** - View title, artist, album, duration, bitrate, file size, and more
- **Remote Playback** - Play files on the PC running the media server (not in the browser)
- **Play All** - Play all media files in the current folder (generates M3U playlist)
- **File Download** - Download individual media files directly from the browser
- **Search & Filter** - Real-time search across files in the current folder
- **Pagination** - Navigate large folders with configurable page sizes (25, 50, 100, 200, 500)
- **Last Path Memory** - Automatically returns to your last browsed location
- **Folder Management** - Create, edit, and delete media folders from the UI
### Browser Setup
Add media folders in your `config.yaml`:
```yaml
# Media folders for browser
media_folders:
music:
path: "C:\\Users\\YourUsername\\Music"
label: "My Music"
enabled: true
videos:
path: "C:\\Users\\YourUsername\\Videos"
label: "My Videos"
enabled: true
# Thumbnail size: "small" (150x150), "medium" (300x300), or "both"
thumbnail_size: "medium"
```
### How Playback Works
When you play a file from the Media Browser:
1. The file is opened using the **default system media player** on the PC running the media server
2. This is designed for **remote control scenarios** where you browse media from one device (e.g., Home Assistant dashboard, phone) but want audio to play on the PC
3. The media player must support the **Windows Media Session API** for playback tracking
### Media Player Compatibility
**Important Limitation:** Not all media players expose their playback information to the Windows Media Session API. This means some players will open and play the file, but the Media Server UI won't show playback status, track information, or allow remote control.
**Compatible Players** (work with playback tracking):
- **VLC Media Player** - Full support
- **Groove Music** (Windows 10/11 built-in) - Full support
- **Spotify** - Full support (if already running)
- **Chrome/Edge/Firefox** - Full support for web players
- **foobar2000** - Full support (with proper configuration/plugins)
**Limited/No Support:**
- **Windows Media Player Classic** - Opens files but doesn't expose session info
- **Windows Media Player** (classic version) - Limited session support
**Recommendation:** Set **VLC Media Player** or **Groove Music** as your default audio player for the best experience with the Media Browser.
#### Changing Your Default Media Player (on Windows)
1. Open Windows Settings > Apps > Default apps
2. Search for "Music player" or "Video player"
3. Select VLC Media Player or Groove Music
4. Files opened from Media Browser will now use the selected player
## Display Control
The Display Control feature allows you to manage monitor brightness and power state from the Web UI or via API.
- **Brightness adjustment** - Set brightness (0-100%) for each monitor
- **Power management** - Turn monitors on or off
- **Multi-monitor support** - See all connected monitors with model, manufacturer, and resolution info
- **Technology**: DDC-CI on Windows, XRandR/ACPI on Linux, IOKit on macOS
### Display API
| Endpoint | Method | Body | Description |
|------------------------------------------|----------|---------------------------|--------------------------------------------------|
| `/api/display/monitors` | GET | - | List monitors (use `?refresh=true` to refresh) |
| `/api/display/brightness/{monitor_id}` | POST | `{"brightness": 0-100}` | Set monitor brightness |
| `/api/display/power/{monitor_id}` | POST | `{"on": true\ | false}` |
**Monitor response:**
```json
{
"id": 0,
"name": "Monitor Name",
"brightness": 100,
"power_supported": true,
"power_on": true,
"model": "Model Number",
"manufacturer": "Manufacturer",
"resolution": "1920x1080",
"is_primary": true
}
```
## Header Links
Configure quick-access links that appear in the Web UI header bar with custom icons.
### Links Setup
Add links in your `config.yaml`:
```yaml
links:
spotify:
url: "https://open.spotify.com"
icon: "mdi:spotify"
label: "Spotify"
settings:
url: "https://your-server.com/settings"
icon: "mdi:cog"
label: "Settings"
description: "System settings"
```
### Links API
| Endpoint | Method | Body | Description |
|-----------------------------------|----------|-------------------------------------|------------------|
| `/api/links/list` | GET | - | List all links |
| `/api/links/create/{link_name}` | POST | `{url, icon, label, description}` | Create link |
| `/api/links/update/{link_name}` | PUT | `{url, icon, label, description}` | Update link |
| `/api/links/delete/{link_name}` | DELETE | - | Delete link |
All connected WebSocket clients receive a `links_changed` notification when links are modified.
## Requirements
- Python 3.10+
- Platform-specific dependencies (see below)
## Installation
Dependencies are declared in `pyproject.toml`. Pick the extra that matches
your OS — the Python deps differ enough between Windows / Linux / macOS
that there's no single `pip install` line.
### Installing on Windows
```bash
pip install ".[windows]"
```
Pulls in `winsdk`, `pywin32`, `pycaw`, `comtypes`, `pystray`, etc.
### Installing on Linux
```bash
# System packages required to build dbus-python + PyGObject from sdist.
sudo apt-get install -y python3-pip python3-venv \
libdbus-1-dev libglib2.0-dev pkg-config
pip install ".[linux]"
```
### Installing on macOS
```bash
pip install ".[macos]"
```
Pulls in `pyobjc-framework-Cocoa` + `pyobjc-framework-Quartz` for the
foreground-window probe; AppleScript-based media control uses the
built-in `osascript`.
### Installing on Android (Termux)
```bash
# In Termux
pkg install python termux-api
pip install "."
```
Requires Termux and Termux:API apps from F-Droid.
## Quick Start
1. Generate configuration with API token:
```bash
python -m media_server.main --generate-config
```
2. View your API token:
```bash
python -m media_server.main --show-token
```
3. Start the server:
```bash
python -m media_server.main
```
4. **Open the Web UI** (recommended):
- Navigate to `http://localhost:8765/` in your browser
- Enter your API token from step 2
- Start playing media and control it from the web interface!
5. Or test via API:
```bash
# Health check (no auth required)
curl http://localhost:8765/api/health
# Get media status
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8765/api/media/status
```
## Configuration Reference
Configuration file locations:
- Windows: `%APPDATA%\media-server\config.yaml`
- Linux/macOS: `~/.config/media-server/config.yaml`
### Full config.yaml Example
```yaml
host: 0.0.0.0
port: 8765
# API Tokens - Multiple tokens with labels for client identification
api_tokens:
home_assistant: "your-home-assistant-token-here"
mobile: "your-mobile-app-token-here"
web_ui: "your-web-ui-token-here"
poll_interval: 1.0
log_level: INFO
# Audio device for system volume control (null = default device)
audio_device: null
# Audio visualizer (requires soundcard + numpy)
visualizer_enabled: true
visualizer_fps: 30
visualizer_bins: 32
visualizer_device: null # null = auto-detect loopback
# Media folders for browser
media_folders:
music:
path: "C:\\Users\\YourUsername\\Music"
label: "My Music"
enabled: true
# Thumbnail size: "small" (150x150), "medium" (300x300), or "both"
thumbnail_size: "medium"
# Custom scripts (execute via API/UI)
scripts:
lock_screen:
command: "rundll32.exe user32.dll,LockWorkStation"
label: "Lock Screen"
description: "Lock the workstation"
icon: "mdi:lock"
timeout: 5
shell: true
# Callbacks (execute after media actions)
callbacks:
on_turn_off:
command: "rundll32.exe user32.dll,LockWorkStation"
timeout: 5
shell: true
# Header quick links
links:
spotify:
url: "https://open.spotify.com"
icon: "mdi:spotify"
label: "Spotify"
```
### Authentication
The media server supports multiple API tokens with friendly labels. This allows you to:
- Issue different tokens for different clients (Home Assistant, mobile apps, web UI, etc.)
- Identify which client is making requests in the server logs
- Revoke individual tokens without affecting other clients
**Token labels** appear in all server logs, making it easy to track and debug client connections:
```text
2026-02-06 03:36:20,806 - media_server.services.websocket_manager - [home_assistant] - INFO - WebSocket client connected
2026-02-06 03:28:24,258 - media_server.routes.scripts - [mobile] - INFO - Executing script: lock_screen
```
**Viewing your tokens:**
```bash
python -m media_server.main --show-token
```
Output:
```text
Config directory: C:\Users\...\AppData\Roaming\media-server
API Tokens:
home_assistant B04zhGDjnxH6LIwxL3VOT0F4qORwaipD7LoDyeAG4EU
mobile xyz123...
web_ui abc456...
```
### Environment Variables
All settings can be overridden with environment variables (prefix: `MEDIA_SERVER_`):
```bash
export MEDIA_SERVER_HOST=0.0.0.0
export MEDIA_SERVER_PORT=8765
export MEDIA_SERVER_LOG_LEVEL=DEBUG
```
**Note:** For multi-token configuration, use the config.yaml file. Environment variables only support single-token mode.
## API Reference
### Health Check
```text
GET /api/health
```
No authentication required. Returns server status and platform info.
**Response:**
```json
{
"status": "healthy",
"platform": "Windows",
"version": "1.0.0"
}
```
### Get Media Status
```text
GET /api/media/status
Authorization: Bearer <token>
```
**Response:**
```json
{
"state": "playing",
"title": "Song Title",
"artist": "Artist Name",
"album": "Album Name",
"album_art_url": "https://...",
"duration": 240.5,
"position": 120.3,
"volume": 75,
"muted": false,
"source": "Spotify"
}
```
### Album Artwork
```text
GET /api/media/artwork
Authorization: Bearer <token>
```
Returns current album artwork as PNG/JPEG/WebP binary. Also accepts token as a query parameter.
### Media Controls
All control endpoints require authentication and return `{"success": true}` on success.
| Endpoint | Method | Body | Description |
|-------------------------|----------|------------------------|--------------------------------|
| `/api/media/play` | POST | - | Resume playback |
| `/api/media/pause` | POST | - | Pause playback |
| `/api/media/stop` | POST | - | Stop playback |
| `/api/media/next` | POST | - | Next track |
| `/api/media/previous` | POST | - | Previous track |
| `/api/media/volume` | POST | `{"volume": 75}` | Set volume (0-100) |
| `/api/media/mute` | POST | - | Toggle mute |
| `/api/media/seek` | POST | `{"position": 60.0}` | Seek to position (seconds) |
| `/api/media/turn_on` | POST | - | Execute on_turn_on callback |
| `/api/media/turn_off` | POST | - | Execute on_turn_off callback |
| `/api/media/toggle` | POST | - | Execute on_toggle callback |
### Visualizer API
| Endpoint | Method | Body | Description |
|-----------------------------------|----------|-----------------------------------------|----------------------------------------|
| `/api/media/visualizer/status` | GET | - | Check availability and running state |
| `/api/media/visualizer/devices` | GET | - | List loopback audio devices |
| `/api/media/visualizer/device` | POST | `{"device_name": "..." \ | null}` |
### Audio Devices
```text
GET /api/audio/devices
Authorization: Bearer <token>
```
Returns a list of available audio output devices.
### Script Management
Scripts can be managed via API or directly from the Web UI (Quick Actions tab).
![Script and Callback Management](docs/scripts-management.PNG)
| Endpoint | Method | Body | Description |
|----------------------------------------|----------|---------------------------------------------------------|--------------------|
| `/api/scripts/list` | GET | - | List all scripts |
| `/api/scripts/execute/{script_name}` | POST | `{"args": []}` | Execute a script |
| `/api/scripts/create/{script_name}` | POST | `{command, label, description, icon, timeout, shell}` | Create a script |
| `/api/scripts/update/{script_name}` | PUT | `{command, label, description, icon, timeout, shell}` | Update a script |
| `/api/scripts/delete/{script_name}` | DELETE | - | Delete a script |
**Execute response:**
```json
{
"success": true,
"script": "lock_screen",
"exit_code": 0,
"stdout": "",
"stderr": ""
}
```
### Script Config Options
Add scripts in your `config.yaml`:
```yaml
scripts:
lock_screen:
command: "rundll32.exe user32.dll,LockWorkStation"
label: "Lock Screen"
description: "Lock the workstation"
timeout: 5
shell: true
shutdown:
command: "shutdown /s /t 0"
label: "Shutdown"
description: "Shutdown the PC immediately"
timeout: 10
shell: true
restart:
command: "shutdown /r /t 0"
label: "Restart"
description: "Restart the PC"
timeout: 10
shell: true
hibernate:
command: "shutdown /h"
label: "Hibernate"
description: "Hibernate the PC"
timeout: 10
shell: true
sleep:
command: "rundll32.exe powrprof.dll,SetSuspendState 0,1,0"
label: "Sleep"
description: "Put PC to sleep"
timeout: 10
shell: true
```
Script fields:
| Field | Required | Description |
|-----------------|------------|--------------------------------------------------------|
| `command` | Yes | Command to execute |
| `label` | No | User-friendly display name (defaults to script name) |
| `description` | No | Description of what the script does |
| `icon` | No | Custom MDI icon (e.g., `mdi:power`) |
| `timeout` | No | Execution timeout in seconds (default: 30, max: 300) |
| `working_dir` | No | Working directory for the command |
| `shell` | No | Run in shell (default: true) |
### Callback Management
Callbacks are commands executed after media actions. They can be managed via API or the Web UI (Quick Actions tab).
| Endpoint | Method | Body | Description |
|-------------------------------------------|----------|--------------------------------------------|--------------------------|
| `/api/callbacks/list` | GET | - | List all callbacks |
| `/api/callbacks/execute/{callback_name}` | POST | - | Execute (for debugging) |
| `/api/callbacks/create/{callback_name}` | POST | `{command, timeout, working_dir, shell}` | Create a callback |
| `/api/callbacks/update/{callback_name}` | PUT | `{command, timeout, working_dir, shell}` | Update a callback |
| `/api/callbacks/delete/{callback_name}` | DELETE | - | Delete a callback |
### Callback Config Options
Add callbacks in your `config.yaml`:
```yaml
callbacks:
# Media control callbacks (run after successful action)
on_play:
command: "echo Play triggered"
timeout: 10
shell: true
on_pause:
command: "echo Pause triggered"
timeout: 10
shell: true
on_stop:
command: "echo Stop triggered"
timeout: 10
shell: true
on_next:
command: "echo Next track"
timeout: 10
shell: true
on_previous:
command: "echo Previous track"
timeout: 10
shell: true
on_volume:
command: "echo Volume changed"
timeout: 10
shell: true
on_mute:
command: "echo Mute toggled"
timeout: 10
shell: true
on_seek:
command: "echo Seek triggered"
timeout: 10
shell: true
# Turn on/off/toggle (callback-only actions, no default behavior)
on_turn_on:
command: "echo PC turned on"
timeout: 10
shell: true
on_turn_off:
command: "rundll32.exe user32.dll,LockWorkStation"
timeout: 5
shell: true
on_toggle:
command: "echo Toggle triggered"
timeout: 10
shell: true
```
Available callbacks:
| Callback | Triggered by | Description |
|-----------------|-------------------------|---------------------------------|
| `on_play` | `/api/media/play` | After play succeeds |
| `on_pause` | `/api/media/pause` | After pause succeeds |
| `on_stop` | `/api/media/stop` | After stop succeeds |
| `on_next` | `/api/media/next` | After next track succeeds |
| `on_previous` | `/api/media/previous` | After previous track succeeds |
| `on_volume` | `/api/media/volume` | After volume change succeeds |
| `on_mute` | `/api/media/mute` | After mute toggle |
| `on_seek` | `/api/media/seek` | After seek succeeds |
| `on_turn_on` | `/api/media/turn_on` | Callback-only action |
| `on_turn_off` | `/api/media/turn_off` | Callback-only action |
| `on_toggle` | `/api/media/toggle` | Callback-only action |
Callback fields:
| Field | Required | Description |
|-----------------|------------|--------------------------------------------------------|
| `command` | Yes | Command to execute |
| `timeout` | No | Execution timeout in seconds (default: 30, max: 300) |
| `working_dir` | No | Working directory for the command |
| `shell` | No | Run in shell (default: true) |
### Browser API
| Endpoint | Method | Body | Description |
|---------------------------------------------|----------|----------------------------------------|--------------------------------------------|
| `/api/browser/folders` | GET | - | List configured media folders |
| `/api/browser/browse` | GET | - | Browse directory (query: folder_id, path) |
| `/api/browser/metadata` | GET | - | Get file metadata (query: file_path) |
| `/api/browser/thumbnail` | GET | - | Get thumbnail image (query: file_path) |
| `/api/browser/download` | GET | - | Download file (query: folder_id, path) |
| `/api/browser/play` | POST | `{"file_path": "..."}` | Open file with default player |
| `/api/browser/play-folder` | POST | `{"folder_id": "...", "path": ""}` | Play all files in folder (M3U) |
| `/api/browser/folders/create` | POST | `{folder_id, label, path, enabled}` | Create folder config |
| `/api/browser/folders/update/{folder_id}` | PUT | `{label, path, enabled}` | Update folder config |
| `/api/browser/folders/delete/{folder_id}` | DELETE | - | Delete folder config |
All endpoints require bearer token authentication.
### Security Notes
- **Path Traversal Protection** - All paths are validated to prevent directory traversal attacks
- **Folder Restrictions** - Only configured folders are accessible
- **Authentication Required** - All endpoints require a valid API token
- **Output Limits** - Script stdout/stderr capped at 10KB
### WebSocket
```text
WebSocket /api/media/ws
Authorization: Bearer <token>
```
The WebSocket connection provides real-time updates and low-latency control.
**Messages from server:**
| Type | Data | Description |
|---------------------|------------------------|----------------------------------------|
| `status` | Media status object | Initial status on connection |
| `status_update` | Media status object | Status changes during playback |
| `audio_data` | `[0.1, 0.2, ...]` | Visualizer frequency data (30 fps) |
| `scripts_changed` | `{}` | Scripts were created/updated/deleted |
| `links_changed` | `{}` | Links were created/updated/deleted |
| `pong` | - | Response to client ping |
| `error` | `{"message": "..."}` | Error messages |
**Messages from client:**
| Type | Data | Description |
|-----------------------|------------------------|------------------------------------------------------------|
| `ping` | - | Keepalive ping |
| `get_status` | - | Request current status |
| `volume` | `{"volume": 0-100}` | Low-latency volume control via WebSocket |
| `enable_visualizer` | - | Subscribe to audio data (starts capture) |
| `disable_visualizer` | - | Unsubscribe from audio data (stops capture on last client) |
## Running as a Service
### Windows Task Scheduler (Recommended)
Run in **Administrator PowerShell** from the project root:
```powershell
.\media_server\service\install_task_windows.ps1
```
To remove the scheduled task:
```powershell
Unregister-ScheduledTask -TaskName "MediaServer" -Confirm:$false
```
### Windows Service (Alternative)
Install:
```bash
python -m media_server.service.install_windows install
```
Start/Stop:
```bash
python -m media_server.service.install_windows start
python -m media_server.service.install_windows stop
```
Remove:
```bash
python -m media_server.service.install_windows remove
```
### Linux (systemd)
Install:
```bash
sudo ./service/install_linux.sh install
```
**Enable user lingering** — required so `/run/user/$UID/bus` (the D-Bus
session socket needed for MPRIS) exists even when no graphical session
is active. Without this the server boots but every `/api/media/*` call
silently returns idle.
```bash
sudo loginctl enable-linger $USER
```
Enable and start the templated unit for your user:
```bash
sudo systemctl enable --now media-server@$USER
```
View logs:
```bash
journalctl -u media-server@$USER -f
```
**Troubleshooting:**
- *"`/api/media/status` always returns `idle`"* — check the service log for
`D-Bus session bus not available`. Most commonly: lingering isn't
enabled, or the unit is using the wrong `XDG_RUNTIME_DIR` (`%U` must
expand to the user's numeric UID).
- *"Visualizer permanently unavailable"* — PulseAudio/PipeWire must
expose monitor sources. `pactl list sources short | grep monitor`
should list at least one entry; if not, install `pipewire-pulse` and
restart your session.
- *"Volume control silently fails"* — `pactl` must be on `PATH` and the
user's PulseAudio/PipeWire server must be reachable
(`PULSE_RUNTIME_PATH=/run/user/$UID/pulse`).
- *"Foreground window is always `null`"* — expected under Wayland; the
compositor hides window info from unprivileged clients. X11 sessions
work normally.
### macOS (LaunchAgent)
The distribution tarball ships an installer:
```bash
./install-launchagent.sh
```
This drops `~/Library/LaunchAgents/com.dolgolyov.media-server.plist`,
starts the service immediately, and re-launches it at every login. Logs
go to `~/Library/Logs/media-server/{stdout,stderr}.log`. To stop:
```bash
./uninstall-launchagent.sh
```
## Command Line Options
```text
python -m media_server.main [OPTIONS]
Options:
--host TEXT Host to bind to (default: 0.0.0.0)
--port INTEGER Port to bind to (default: 8765)
--generate-config Generate default config file and exit
--show-token Show current API token and exit
```
## Security Recommendations
1. **Use HTTPS in production** - Set up a reverse proxy (nginx, Caddy) with SSL
2. **Strong tokens** - Default tokens are 32 random characters; don't use weak tokens
3. **Firewall** - Only expose the port to trusted networks
4. **Secrets management** - Don't commit tokens to version control
## Supported Media Players
### Players on Windows
- Spotify
- Windows Media Player
- VLC
- Groove Music
- foobar2000
- AIMP
- Web browsers (Chrome, Edge, Firefox, Opera, Brave)
- Any app using Windows Media Transport Controls
### Players on Linux
- Any MPRIS-compliant player:
- Spotify
- VLC
- Rhythmbox
- Clementine
- Web browsers
- MPD (with MPRIS bridge)
### Players on macOS
- Spotify
- Apple Music
- VLC (partial)
- QuickTime Player
### Players on Android (via Termux)
- System media controls
- Limited seek support
## Troubleshooting
### "No active media session"
- Ensure a media player is running and has played content
- On Windows, check that the app supports media transport controls
- On Linux, verify MPRIS with: `dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep mpris`
### Permission errors on Linux
- Ensure your user has access to the D-Bus session bus
- For systemd service, the `DBUS_SESSION_BUS_ADDRESS` must be set correctly
### Volume control not working
- Windows: Run as administrator if needed
- Linux: Ensure PulseAudio/PipeWire is running
## License
MIT License