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 method | Protocol | Apple TV 3 |
|---|---|---|
| Control Center remote (iPhone) | companion-link | No, tvOS only |
| TV remote | HDMI-CEC | No, missing from the hardware |
| POP TV Go app (iPhone) | AirPlay | Yes, but video only |
| Mac with pyatv | DMAP, port 3689 | Yes, full navigation |
| Infrared remote | IR, Learn Remote | Yes, 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.
- Navigate to the Learn Remote menu with
atv.py. - Point the TV remote at the Apple TV.
- Press the chosen button for each requested function: up, down, left, right, select, Menu.
- 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.
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.