← Projects
MIT · Open source

androidtv-remote

Control Android TV / Google TV devices over the Android TV Remote v2 protocol (TypeScript)

A modern TypeScript/ESM implementation of the Android TV Remote v2 protocol — pair with a device over TLS, then send keys, launch apps, and inject text directly from Node.

🌟 Features

  • 🔑 First-class pairing — handles the full certificate-based TLS pairing flow and persists credentials for reconnect
  • ⌨️ Native text inputsendText() uses IME injection to type arbitrary strings, no keycode mapping required
  • 📺 Full key control — send any Android key via RemoteKeyCode constants (navigation, media, DPAD, volume, and more)
  • 📡 State events — subscribe to powered, volume, current_app, and unpaired with typed payloads
  • 🪶 Lean dependenciescrypto-js, systeminformation, and core-js dropped; relies only on node-forge and protobufjs
  • 🔇 Silent by default — all internal logging is suppressed unless debug: true is set, so it fits cleanly inside a larger application
  • 🏷️ Typed API — full TypeScript types exported; on(), once(), and emit() are all narrowed to the event map

🚀 Quick Start

npm install @kud/androidtv-remote

Pair a new device

import { createAndroidRemote } from "@kud/androidtv-remote"

const remote = createAndroidRemote("192.168.1.42")

remote.on("secret", () => {
  // The TV shows a pairing PIN — read it from stdin and submit
  remote.sendCode("123456")
})

remote.on("ready", () => {
  console.log("paired and connected")
  remote.sendPower()
})

await remote.start()

// Persist the certificate so you skip pairing next time
const cert = remote.getCertificate()

Reconnect with a saved certificate

import { createAndroidRemote } from "@kud/androidtv-remote"

const remote = createAndroidRemote("192.168.1.42", {
  cert: { key: savedKey, cert: savedCert },
})

remote.on("ready", () => {
  remote.sendText("Hello, TV!")
})

await remote.start()

📖 API Reference

createAndroidRemote(host, options?)

Factory function that returns an AndroidRemote instance — an EventEmitter extended with control methods.

import { createAndroidRemote } from "@kud/androidtv-remote"

const remote = createAndroidRemote("192.168.1.42", options)

Options

OptionTypeDefaultDescription
cert{ key: string; cert: string }Previously obtained certificate. Skips pairing when provided.
pairing_portnumber6467TCP port for the pairing handshake.
remote_portnumber6466TCP port for the remote control session.
service_namestring"androidtv-remote"Name sent to the TV during pairing.
manufacturerstringDevice manufacturer reported during pairing.
modelstringDevice model reported during pairing.
debugbooleanfalseEnable verbose internal logging.

Methods

MethodReturnsDescription
start()Promise<boolean>Connect to the TV. Triggers pairing if no certificate is set.
sendCode(code)booleanSubmit the PIN displayed on the TV during pairing.
sendKey(keyCode, direction?)voidSend a key press. Use RemoteKeyCode constants.
sendPower()voidToggle the TV power state.
sendAppLink(link)voidLaunch an app via its deep-link URI.
sendText(text)voidInject arbitrary text via IME (no keycode mapping).
getCertificate()CertificateReturn the current { key, cert } pair for persistence.
stop()voidClose all connections.

Events

EventPayloadFired when
secretThe TV is showing a pairing PIN. Call sendCode().
readyConnected and ready for commands.
poweredbooleanTV power state changed.
volume{ level: number; maximum: number; muted: boolean }Volume state changed.
current_appstringForeground app changed (package name).
unpairedThe TV rejected or revoked the certificate.
errorErrorA protocol-level error occurred.

RemoteKeyCode

An enum of Android key codes. Common entries:

import { RemoteKeyCode } from "@kud/androidtv-remote"

remote.sendKey(RemoteKeyCode.KEYCODE_HOME)
remote.sendKey(RemoteKeyCode.KEYCODE_BACK)
remote.sendKey(RemoteKeyCode.KEYCODE_DPAD_UP)
remote.sendKey(RemoteKeyCode.KEYCODE_MEDIA_PLAY_PAUSE)
remote.sendKey(RemoteKeyCode.KEYCODE_VOLUME_UP)

RemoteDirection

Direction constants for sendKey() — defaults to SHORT (a normal press). Use START_LONG / END_LONG to bracket a long press:

import { RemoteKeyCode, RemoteDirection } from "@kud/androidtv-remote"

remote.sendKey(RemoteKeyCode.KEYCODE_DPAD_UP) // SHORT by default
remote.sendKey(RemoteKeyCode.KEYCODE_DPAD_DOWN, RemoteDirection.START_LONG)
remote.sendKey(RemoteKeyCode.KEYCODE_DPAD_DOWN, RemoteDirection.END_LONG)

setDebug(enabled)

Enable or disable verbose logging globally (affects all instances):

import { setDebug } from "@kud/androidtv-remote"

setDebug(true)

🔧 Development

androidtv-remote/
├── src/
│   ├── index.ts                        # Public API and factory
│   ├── types.ts                        # Shared TypeScript types
│   ├── device-info.ts                  # Device metadata sent during pairing
│   ├── logger.ts                       # Debug-gated logger
│   ├── certificate/
│   │   └── certificate-generator.ts   # TLS cert generation via node-forge
│   ├── pairing/
│   │   ├── pairing-manager.ts         # Pairing session state machine
│   │   ├── pairing-message-manager.ts
│   │   └── pairingmessage.proto
│   └── remote/
│       ├── remote-manager.ts          # Remote control session
│       ├── remote-message-manager.ts  # Key/text encoding
│       └── remotemessage.proto
└── dist/                              # Compiled output (tsup)
git clone https://github.com/kud/androidtv-remote.git
cd androidtv-remote
npm install
npm run build

Scripts

ScriptDescription
npm run buildCompile TypeScript to dist/ via tsup
npm run build:watchRecompile on file changes
npm run typecheckType-check without emitting
npm testRun tests with tsx
npm run cleanRemove dist/

🏗 Tech Stack

PackageRole
node-forgeTLS certificate generation and mutual TLS
protobufjsEncode/decode Android TV Remote v2 protocol buffers
tsupZero-config TypeScript bundler
tsxTypeScript test runner

Credits

Derived from androidtv-remote by louis49 (MIT). The reverse-engineered Android TV Remote v2 protocol and .proto schemas originate from that project. This library is a TypeScript rewrite with a functional API, native text input support, and a leaner dependency footprint.


MIT © kud