Add local ADB web remote for Android TV

Node/Express server shells out to adb (connect/pair/keyevent/text/app
launch) and serves a mobile-first installable PWA remote: power, mute,
input switch, volume/channel rockers, D-pad, media transport, text
entry, and one-tap streaming app launch. Supports multiple saved TVs
and the Android 11+ wireless-debugging pairing flow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSVyYcrjrbmrMKT85G4fyn
This commit is contained in:
Claude
2026-09-26 12:40:01 +00:00
commit 6db1db70a0
18 changed files with 2246 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no" />
<title>TV Remote</title>
<meta name="theme-color" content="#0b0d12" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/icons/icon-192.png" />
<link rel="apple-touch-icon" href="/icons/icon-192.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header class="topbar">
<div class="device-picker">
<select id="deviceSelect" aria-label="Select TV"></select>
<span id="statusDot" class="status-dot" title="Connection status"></span>
</div>
<button id="settingsBtn" class="icon-btn" aria-label="Settings">⚙</button>
</header>
<main id="remote" class="remote">
<div class="row row-top">
<button class="btn btn-round" data-key="POWER" aria-label="Power">⏻</button>
<button class="btn btn-round" data-key="TV_INPUT" aria-label="Input">⇄</button>
<button class="btn btn-round" data-action="mute" aria-label="Mute">🔇</button>
</div>
<div class="dpad">
<button class="btn dpad-up" data-key="DPAD_UP" aria-label="Up">▲</button>
<button class="btn dpad-left" data-key="DPAD_LEFT" aria-label="Left">◀</button>
<button class="btn dpad-center" data-key="DPAD_CENTER" aria-label="OK">OK</button>
<button class="btn dpad-right" data-key="DPAD_RIGHT" aria-label="Right">▶</button>
<button class="btn dpad-down" data-key="DPAD_DOWN" aria-label="Down">▼</button>
</div>
<div class="row row-nav">
<button class="btn" data-key="BACK" aria-label="Back">⤺ Back</button>
<button class="btn" data-key="HOME" aria-label="Home">⌂ Home</button>
<button class="btn" data-key="MENU" aria-label="Menu">☰ Menu</button>
</div>
<div class="row row-vol-ch">
<div class="rocker" data-rocker="volume">
<span class="rocker-label">VOL</span>
<button class="btn rocker-btn" data-key="VOLUME_UP" aria-label="Volume up">+</button>
<button class="btn rocker-btn" data-key="VOLUME_DOWN" aria-label="Volume down">−</button>
</div>
<div class="rocker" data-rocker="channel">
<span class="rocker-label">CH</span>
<button class="btn rocker-btn" data-key="CHANNEL_UP" aria-label="Channel up">+</button>
<button class="btn rocker-btn" data-key="CHANNEL_DOWN" aria-label="Channel down">−</button>
</div>
</div>
<div class="row row-media">
<button class="btn" data-key="MEDIA_REWIND" aria-label="Rewind">⏪</button>
<button class="btn" data-key="MEDIA_PLAY_PAUSE" aria-label="Play/Pause">⏯</button>
<button class="btn" data-key="MEDIA_FAST_FORWARD" aria-label="Fast forward">⏩</button>
</div>
<form class="row row-search" id="searchForm">
<input id="searchInput" type="text" inputmode="text" placeholder="Type to search…" autocomplete="off" />
<button type="submit" class="btn">Send</button>
</form>
<div class="apps" id="appsRow"></div>
</main>
<dialog id="settingsDialog">
<h2>TVs</h2>
<ul id="deviceList" class="device-list"></ul>
<form id="addDeviceForm" class="add-device-form">
<input id="newDeviceName" type="text" placeholder="Name (e.g. Living Room)" autocomplete="off" />
<input id="newDeviceHost" type="text" placeholder="IP address (e.g. 192.168.1.50)" autocomplete="off" required />
<button type="submit" class="btn">Add TV</button>
</form>
<details class="pair-details">
<summary>Pair a new Android 11+ TV</summary>
<form id="pairForm" class="pair-form">
<p>On the TV: Settings → System → Developer options → Wireless debugging → Pair device with pairing code.</p>
<input id="pairHost" type="text" placeholder="Pairing IP:port (e.g. 192.168.1.50:41231)" autocomplete="off" required />
<input id="pairCode" type="text" inputmode="numeric" placeholder="6-digit code" autocomplete="off" required />
<button type="submit" class="btn">Pair</button>
</form>
</details>
<p id="settingsMessage" class="settings-message" role="status"></p>
<button id="closeSettings" class="btn btn-close">Close</button>
</dialog>
<div id="toast" class="toast" role="status" aria-live="polite"></div>
<script src="/app.js"></script>
</body>
</html>