Skip to content

Commit 5743150

Browse files
committed
Send TVRCSessionStart so tvOS 26.5 will return the apps list
tvOS 26.5 silently drops FetchLaunchableApplicationsEvent unless the client first registers a TV remote control session via TVRCSessionStart with ProtocolVersionKey 1.2. Older tvOS replies harmlessly or ignores it; a 2-second timeout in the connect path keeps reconnects fast on legacy firmware in the latter case. Discovered by pyatv: postlund/pyatv#2847
1 parent d3980f5 commit 5743150

2 files changed

Lines changed: 35 additions & 5 deletions

File tree

Sources/ItsytvCore/Protocol/AppleTVManager.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -786,11 +786,24 @@ public final class AppleTVManager {
786786
self?.textInputSessionUUID = result.sessionUUID
787787
}
788788
}
789-
DispatchQueue.main.async {
790-
self?.connectionStatus = .connected
791-
self?.discovery.pause()
792-
self?.flushPendingCompanionCommands()
793-
self?.fetchApps()
789+
// tvOS 26.5+ silently drops FetchLaunchableApplicationsEvent
790+
// unless we register a TV-remote-control session first. On older
791+
// tvOS the device replies harmlessly or ignores it — the 2 s
792+
// timeout below makes sure we still proceed in either case.
793+
var didProceed = false
794+
let proceed: () -> Void = { [weak self] in
795+
DispatchQueue.main.async {
796+
guard !didProceed else { return }
797+
didProceed = true
798+
self?.connectionStatus = .connected
799+
self?.discovery.pause()
800+
self?.flushPendingCompanionCommands()
801+
self?.fetchApps()
802+
}
803+
}
804+
self?.connection?.startTVRCSession { proceed() }
805+
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 2) {
806+
proceed()
794807
}
795808
}
796809
}

Sources/ItsytvCore/Protocol/CompanionCommands.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,23 @@ extension CompanionConnection {
147147
)
148148
}
149149

150+
/// Register a TV Remote Control session. Required precondition for
151+
/// tvOS 26.5+ to serve `FetchLaunchableApplicationsEvent` and
152+
/// `FetchAttentionState`; without this call the launcher daemon
153+
/// silently drops those requests. Older tvOS either responds with empty
154+
/// content or ignores it — either way the rest of the flow keeps working.
155+
///
156+
/// Discovered by pyatv: https://github.com/postlund/pyatv/pull/2847
157+
func startTVRCSession(completion: @escaping () -> Void) {
158+
sendRequest(
159+
eventName: "TVRCSessionStart",
160+
content: .dictionary([
161+
("ProtocolVersionKey", .string("1.2")),
162+
]),
163+
responseHandler: { _ in completion() }
164+
)
165+
}
166+
150167
/// Fetch the list of launchable applications.
151168
func fetchApps(completion: @escaping ([(bundleID: String, name: String)]) -> Void) {
152169
sendRequest(eventName: "FetchLaunchableApplicationsEvent", content: .dict([]), responseHandler: { response in

0 commit comments

Comments
 (0)