Centralized ADB control for a fleet of Android TV / Fire TV devices, wrapped in a single Docker container with a metrics dashboard.
What it adds on top of scavin/ws-scrcpy:
- ADB-managed device pool — connects to all your devices on
startup so they show up in
ws-scrcpy's device list immediately (no per-deviceadb connectclicking) - Metrics dashboard at
:8095— Glances-style cards per device with CPU / RAM / disk / temps / running app / top-5 processes / screenshot - JSON API at
:8095/api(and root) — same data structured for Homepagecustomapiwidgets or your own scrape - Per-device screenshots at
:8095/screenshot/<Name>— refreshed on each poll cycle, suitable for Homepage thumbnail widgets - One-click ADB shell or screen mirror from each dashboard card
- ws-scrcpy quality-of-life patches — bumps default bitrate from ~500 kbps to 4 Mbps and forces fit-to-screen so the stream actually fills the iframe instead of showing a tiny corner
Out of the box ws-scrcpy requires re-establishing ADB connections
manually each time the container starts. With a fleet of always-on
streaming devices that get rebooted occasionally, that's tedious.
It also shows nothing useful at-a-glance — you have to drill into each device to see if it's even online. The metrics dashboard gives a single page summary of the whole fleet that you can drop into Homepage (or any iframe-friendly dashboard) and click through to the screen mirror or shell when something looks off.
Anything with ADB over network support, which in practice is:
- NVIDIA SHIELD Android TV (any model with developer mode + network ADB)
- Amazon Fire TV (any generation with ADB enabled)
- Google Chromecast w/ Google TV
- Google TV Streamer
- Sideloaded Android TV boxes (whatever you've got)
Tested on a mixed fleet of NVIDIA SHIELDs and Fire TVs. Should work with anything ADB can connect to over IP.
git clone https://github.com/rabidfurball/adb-atv-manager.git
cd adb-atv-manager
# Edit docker-compose.yml — set ADB_DEVICES + SCRCPY_HOST_URL
docker compose up -dThen open the dashboard at http://<docker-host>:8095 and the raw
ws-scrcpy at http://<docker-host>:8096.
Each device needs to authorize this container's ADB key the
first time it tries to connect. After docker compose up -d:
- The container will try to
adb connectto each device on startup - Each device should show an "Allow USB debugging?" prompt with the container's RSA fingerprint
- Tick "Always allow from this computer" on the device screen, then OK
- The connection should then succeed (visible in
docker compose logs adb)
The ADB keys are persisted in a named Docker volume (adb-keys) so
this only happens once per device.
To enable ADB debugging on a fresh device:
- NVIDIA SHIELD: Settings → Device Preferences → About → tap Build 7 times → back → Developer Options → Network debugging ON
- Fire TV: Settings → My Fire TV → About → tap Serial Number 7 times → back → Developer Options → ADB Debugging ON
All via environment variables in docker-compose.yml:
| Var | Default | Purpose |
|---|---|---|
ADB_DEVICES |
(none — required) | Comma-separated Name:IP pairs. Names appear in the dashboard, IPs are the LAN address of each device. Port 5555 is implied. |
SCRCPY_HOST_URL |
http://localhost:8096 |
Public URL for ws-scrcpy. Used to build mirror links and WebSocket URLs in the dashboard. Must match the URL your browser will use to reach port 8096 of this container. |
POLL_INTERVAL |
30 |
Seconds between metrics polls. Lower = fresher data, higher = lower load on devices. 30 is a reasonable default. |
METRICS_PORT |
8080 (internal) |
Port the metrics server listens on inside the container. The compose file maps it to host 8095. |
SCRCPY_PORT |
8000 (internal) |
Port ws-scrcpy listens on inside the container. The compose file maps it to host 8096. |
TZ |
UTC |
Timezone for log timestamps. |
To add a new device after first deploy: edit ADB_DEVICES, run
docker compose up -d adb (recreates the container), accept the
ADB authorization prompt on the new device's screen.
| URL | Purpose |
|---|---|
http://<host>:8095/ |
Dashboard (HTML) — auto-refreshes every 30s |
http://<host>:8095/api |
JSON metrics — full payload of all devices |
http://<host>:8095/screenshot/<Name> |
Latest PNG screenshot for one device |
http://<host>:8095/refresh |
Trigger an out-of-cycle metrics poll (for "refresh now" buttons) |
http://<host>:8096/ |
Raw ws-scrcpy UI — device list, dev tools, file manager |
The /api endpoint returns:
{
"timestamp": "2026-04-24T01:30:00Z",
"devices": [
{
"name": "Theatre",
"ip": "192.168.1.10",
"status": "online",
"model": "SHIELD Android TV",
"cpu_user": 12,
"cpu_idle": 80,
"mem_total_mb": 3000,
"mem_avail_mb": 1200,
"storage_total": "13G",
"storage_used": "9G",
"storage_pct": 73,
"cpu_temp": 45.2,
"gpu_temp": 50.8,
"app": "com.plexapp.android",
"top_processes": [
{"pid": 1234, "cpu": "10", "mem": "5", "name": "com.plexapp.android"}
]
}
]
}Offline devices return only name, ip, and status: "offline".
To show the dashboard as a Homepage widget, use a customapi widget
pointing at the JSON endpoint. Example:
- ATV Fleet:
icon: mdi-android
href: http://<host>:8095
widget:
type: customapi
url: http://<host>:8095/api
mappings:
- field: devices.0.app # currently playing on first device
label: Theatre
- field: devices.1.app
label: BedroomOr use individual iframe widgets pointing at
http://<host>:8095/screenshot/<Name> for thumbnail strips.
MIT — see LICENSE.
The wrapped ws-scrcpy upstream is GPL-3.0; nothing in this repo
modifies that container image's source. We patch its bundled JS at
container start (bitrate + fit-to-screen defaults) and that patch is
applied at runtime, not redistributed.
- scavin/ws-scrcpy — the actual screen-mirror/ADB-proxy container we build on
- NetrisTV/ws-scrcpy — original upstream project
- Built with Claude Code.