Files
tv-adb-remote/README.md
Claude e89683840a Make Docker Compose the default way to run the server
adb only needs plain outbound TCP to the TV, so it runs fine in a
container with normal bridge networking (no host networking or
privileged mode). Bundles adb into the image via Dockerfile, adds
docker-compose.yml with a persisted data/ volume, and documents two
ways to fold this into another docker-compose-based repo (vendored
build context vs. a published image).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSVyYcrjrbmrMKT85G4fyn
2026-09-26 14:54:49 +00:00

138 lines
5.4 KiB
Markdown

# TV ADB Remote
A slick, installable web remote for Android TV, controlled over ADB. Run a
small Node server on any always-on machine on your home network (a Pi, NAS,
old laptop, home server); point your phone's browser at it and control the
TV from a mobile-optimized PWA.
Why a local web app instead of a native Android app: Android TV only
exposes control over ADB once you enable network debugging, and the
official ADB tooling is a desktop program. Rather than reimplement the ADB
protocol inside a phone app, this runs `adb` on a machine that already has
it, and serves a touch-friendly remote UI to any phone/tablet browser on the
LAN. Add to the home screen and it behaves like an installed app.
## Features
- Power, mute, input switch, volume rocker, channel rocker
- D-pad navigation, OK, back, home, menu
- Media transport (rewind / play-pause / fast-forward)
- Text entry (e.g. search boxes) sent as real keystrokes
- One-tap launch for common streaming apps (Netflix, YouTube, Prime Video,
Disney+, Spotify, Plex)
- Manage multiple TVs, each remembered with a friendly name
- Built-in pairing flow for Android 11+ ("Wireless debugging")
- Installable PWA with offline app shell
## Requirements
- Docker and Docker Compose on any always-on machine on your home network (a
Pi, NAS, old laptop, home server).
- The TV and that machine on the same local network.
`adb` does **not** need to run on bare metal — it's a plain TCP client that
dials out to the TV's IP once network/wireless debugging is on, so it works
fine from inside a container with normal bridge networking. No host
networking, USB passthrough, or privileged mode required. The container
image bundles `adb` itself, so nothing else needs installing on the host.
## Set up the TV
1. On the Android TV: Settings → System (or Device Preferences) → About →
tap "Build" repeatedly to enable Developer options.
2. Settings → Developer options → turn on **Network debugging** (older
Android TV) or **Settings → System → Developer options → Wireless
debugging** (Android 11+ / Google TV).
3. Note the IP address shown (Settings → Network & Internet).
- Android TV ≤ 10: that's it — the debug port (5555) is open.
- Android 11+ / Google TV: it shows a separate pairing IP:port and a
6-digit code. Use the app's "Pair a new Android 11+ TV" flow, then add
the TV using its regular IP (still port 5555 once paired).
## Run the server
```sh
docker compose up -d --build
```
This serves the app at `http://<this-machine's-LAN-IP>:8787`. Open that URL
on your phone, add your TV by IP in Settings (⚙), then optionally "Add to
Home Screen" for a full-screen, app-like remote.
Saved TVs persist in `./data/devices.json` on the host (bind-mounted into
the container), so they survive `docker compose down`/rebuilds. Override the
port by changing the `PORT` environment variable and the left side of the
`ports` mapping in `docker-compose.yml`.
### Adding this as a service in another docker-compose stack
If you're folding this into a larger compose project rather than running it
standalone, you have two straightforward options:
1. **Vendor the source** (git submodule or subtree at, say,
`services/tv-adb-remote/`) and point that stack's compose file at it:
```yaml
services:
tv-adb-remote:
build: ./services/tv-adb-remote
ports:
- "8787:8787"
volumes:
- ./services/tv-adb-remote/data:/app/data
restart: unless-stopped
```
2. **Build and push an image** (e.g. to GHCR or Docker Hub) from this repo's
CI, then reference it by tag instead of a build context — no vendored
source in the other repo at all:
```yaml
services:
tv-adb-remote:
image: ghcr.io/<you>/tv-adb-remote:latest
ports:
- "8787:8787"
volumes:
- ./tv-adb-remote-data:/app/data
restart: unless-stopped
```
This fits a "docker nearly exclusively" stack best if most of its other
services are already referenced as prebuilt images rather than local
build contexts — ask if you want a GitHub Actions workflow added here to
publish the image on push.
Either way, the only two things the other repo's compose file needs to get
right are the port mapping and a persistent volume for `/app/data`.
## Development
```sh
cd server
npm install
npm test
```
Runs the server's unit tests (command construction, input validation,
device persistence) using Node's built-in test runner — no real TV, and no
Docker, required. Useful for iterating on server code before rebuilding the
image.
## Project layout
```
Dockerfile Bundles adb + the server into one image
docker-compose.yml Standalone run config (port 8787, data/ volume)
server/ Express app + ADB bridge (source of truth for control logic)
src/adb.js Thin wrapper around the `adb` CLI (connect/pair/keyevent/…)
src/keycodes.js Android KeyEvent constants used by the remote buttons
src/deviceStore.js Persists saved TVs to data/devices.json
src/index.js HTTP API + static file serving
test/ Node test runner specs
public/ The PWA served to phones (HTML/CSS/vanilla JS, no build step)
scripts/ One-off dev scripts (icon generation)
```
## Security note
This server has no authentication and will happily run `adb` commands
against whatever IP you give it — it's designed to sit on your trusted home
LAN, not the public internet. Don't port-forward it.