Skip to content

Commit c6dc811

Browse files
authored
Compute paired= from audio protocols only, not Companion (#28)
PR #27 introduced an "all Mandatory protocols must have credentials" rule for the paired flag. That's too strict: many tvOS devices (including Apple TV 4K on tvOS 18+) list Companion as Mandatory, and Companion is the remote-control protocol, not the audio one. Streaming audio only needs RAOP and AirPlay paired. With the old rule, the Living Room Apple TV came back paired=False even though both RAOP and AirPlay had credentials, which hid it from the stream picker. Now we check only RAOP and AirPlay services when computing paired=. needs_pairing stays gated on RAOP since that's the protocol the Pair button starts pairing with.
1 parent 5466f9c commit c6dc811

1 file changed

Lines changed: 19 additions & 10 deletions

File tree

main.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,21 +1581,30 @@ async def scan_devices():
15811581
hidden = set(state.settings.get("hidden_devices", []))
15821582
custom_names = state.settings.get("device_names", {})
15831583
state.available_devices = []
1584+
# Only the audio protocols matter for our use case. Companion is for
1585+
# remote control and is irrelevant to streaming audio, so don't gate
1586+
# "paired" on it even when it reports Mandatory.
1587+
AUDIO_PROTOS = (pyatv.Protocol.RAOP, pyatv.Protocol.AirPlay)
15841588
for d in found:
1585-
# A device needs pairing if RAOP (the protocol we stream with)
1586-
# has Mandatory pairing and we don't already have credentials.
1589+
# A device needs pairing if RAOP (what we stream with) has
1590+
# Mandatory pairing and we don't already have credentials.
15871591
raop = d.get_service(pyatv.Protocol.RAOP)
15881592
needs_pair = bool(
15891593
raop and str(getattr(raop, "pairing", "")).endswith("Mandatory")
15901594
and not raop.credentials
15911595
)
1592-
# "paired" is true if every Mandatory protocol on this device has
1593-
# credentials. RAOP is what we stream with; AirPlay/Companion are
1594-
# often required alongside it on modern tvOS.
1595-
all_mandatory_paired = True
1596-
for svc in d.services:
1597-
if str(getattr(svc, "pairing", "")).endswith("Mandatory") and not svc.credentials:
1598-
all_mandatory_paired = False
1596+
# "paired" is true if every Mandatory *audio* protocol on this
1597+
# device has credentials. tvOS often requires both RAOP and
1598+
# AirPlay; HomePods often require neither.
1599+
audio_paired = True
1600+
for proto in AUDIO_PROTOS:
1601+
svc = d.get_service(proto)
1602+
if (
1603+
svc
1604+
and str(getattr(svc, "pairing", "")).endswith("Mandatory")
1605+
and not svc.credentials
1606+
):
1607+
audio_paired = False
15991608
break
16001609
state.available_devices.append({
16011610
"id": d.identifier,
@@ -1604,7 +1613,7 @@ async def scan_devices():
16041613
"address": str(d.address),
16051614
"hidden": d.identifier in hidden,
16061615
"needs_pairing": needs_pair,
1607-
"paired": all_mandatory_paired,
1616+
"paired": audio_paired,
16081617
})
16091618
return {"devices": state.available_devices + _get_local_outputs()}
16101619

0 commit comments

Comments
 (0)