Tutoriel

Apple TV without a remote: control it from your Mac

Lost remote, an iPhone stuck on Searching, a TV remote that does nothing. Bonjour, pyatv and 78 lines of Python to take back control of a 2012 Apple TV.

No remote. The iPhone shows Searching forever. The TV remote does nothing. And yet the Apple TV is right there on the network, happily accepting AirPlay. The problem wasn't a fault. It was a matter of generation.

The starting point is mundane. POP TV, the TV service from POST Luxembourg, refuses screen mirroring from the Mac: over AirPlay you get the sound and a black screen. That's DRM/HDCP protection doing its job, not a setting to untick. Over a wired HDMI cable everything works, because the cable negotiates HDCP.

The clean workaround: the POP TV Go app on the iPhone, which sends the video stream over AirPlay instead of mirroring a screen. It works. One detail left: the Apple TV remote went missing long ago.

The symptom: Searching forever

The obvious move is the remote built into the iPhone's Control Center. Choose a TV menu, then Searching. Nothing shows up. Same Wi-Fi, no VPN, Apple TV restarted several times: still nothing.

The puzzling part: from that same iPhone, AirPlay to the Apple TV works perfectly. So the device is visible. Why can't the remote see it?

Listen to the network before touching anything

Apple devices announce themselves on the local network via Bonjour (mDNS). Each service has its own name: _airplay._tcp for AirPlay, _companion-link._tcp for the modern remote. The dns-sd tool, built into every Mac, lets you listen in.

dns-sd -B _companion-link._tcp local.
# rien, a part le Mac lui-meme

dns-sd -L "Apple TV" _airplay._tcp local.
# Apple\032TV._airplay._tcp.local. can be reached at Apple-TV.local.:7000
#  model=AppleTV3,1 srcvers=220.68 osvers=8.4.4

Answer: AppleTV3,1, system 8.4.4. A 2012 Apple TV 3, older than tvOS. It doesn't announce companion-link, the protocol the iPhone remote relies on. It never will. No update, no setting, no pairing can fix that. It has no HDMI-CEC either, which explains why it ignores the TV remote entirely.

Control methodProtocolApple TV 3
Control Center remote (iPhone)companion-linkNo, tvOS only
TV remoteHDMI-CECNo, missing from the hardware
POP TV Go app (iPhone)AirPlayYes, but video only
Mac with pyatvDMAP, port 3689Yes, full navigation
Infrared remoteIR, Learn RemoteYes, once learned

pyatv: ask the Apple TV what it accepts

pyatv is an open source Python library that speaks every Apple TV protocol, old ones included. Its atvremote tool scans a device and lists what it exposes:

uvx --python 3.12 --from pyatv atvremote --scan-hosts <IP> scan

       Name: Apple TV
   Model/SW: Apple TV 3, ATV SW 8.4.4
Services:
 - Protocol: AirPlay, Port: 7000, Pairing: NotNeeded
 - Protocol: DMAP, Port: 3689, Credentials: <identifiant>, Pairing: Optional
 - Protocol: RAOP, Port: 7000, Pairing: NotNeeded

The line that matters: DMAP, with an identifier already present and optional pairing. DMAP is the old iTunes protocol, the one used by the late Remote app. The identifier apparently comes from Home Sharing being enabled on the Apple TV. Result: the Mac can drive the Apple TV without pairing, so without needing a remote to confirm a code.

First trap along the way: with Python 3.14, atvremote crashes with RuntimeError: There is no current event loop. The tool calls asyncio.get_event_loop() outside a running loop, which Python 3.14 now rejects. uvx --python 3.12 solves it without installing anything globally.

A keyboard remote in a single file

Typing one command per key press isn't a remote. So I had Claude Code write a small script, atv.py, 78 lines. Dependencies are declared in the file header (PEP 723), and uv run atv.py handles the rest: right Python version, pyatv, isolated environment.

# /// script
# requires-python = ">=3.12,<3.13"
# dependencies = ["pyatv"]
# ///

How it works: the terminal switches to cbreak mode to read each key without waiting for Enter, each key maps to a pyatv command, and the DMAP connection stays open. Arrows to navigate, Enter to select, Escape for Menu, h for home.

KEYS = {
    "\x1b[A": ("up", "up"),
    "\x1b[B": ("down", "down"),
    "\r": ("select", "OK"),
    "\n": ("select", "OK"),  # the terminal turns Enter into \n
    "\x1b": ("menu", "Menu"),
    "h": ("top_menu", "Home"),
}

conf.set_credentials(Protocol.DMAP, DMAP_CREDENTIALS)
atv = await pyatv.connect(conf, loop)
await atv.remote_control.select()

The Enter key trap

First test: arrows work, Enter does nothing. The script expected \r, the code the key sends. But cbreak mode doesn't switch everything off: the terminal's ICRNL option stays on and turns \r into \n before the program reads it.

Fix: accept both. One line. The kind of bug you never find by rereading the code, only by pressing the key.

Learn Remote: no more Mac

The Mac gets you out of trouble, but nobody wants to open a terminal to watch TV. The Apple TV 3 has a forgotten menu: Settings > General > Remotes > Learn Remote. It learns any infrared remote. The TV's own remote, for instance.

  1. Navigate to the Learn Remote menu with atv.py.
  2. Point the TV remote at the Apple TV.
  3. Press the chosen button for each requested function: up, down, left, right, select, Menu.
  4. Name the remote and confirm.

Once learning is done, the TV remote drives the Apple TV too. The Mac goes back to its day job, the script stays around as a fallback.

Identify the model before hunting for the fault. Three restarts and a Wi-Fi check would never have fixed a generation problem. A single dns-sd command revealed it in seconds.

What I take away from it

  • It works over AirPlay proves nothing. Every Apple feature has its own protocol, and a device can accept one while ignoring another.
  • The network tells the truth. Bonjour announcements give the exact model and system without touching the device.
  • Old hardware has old protocols, not zero protocols. DMAP is obsolete for Apple, not for pyatv.
  • The final fix isn't the script. It's a 2012 menu that makes the TV remote useful. The code was a bridge, not the destination.

Try pyatv

Open source Python library, compatible with Apple TV 2 and 3 all the way to recent tvOS models. The atvremote tool is enough for a first scan.

pyatv on GitHub