Skip to content

Fix Airplay to modern AppleTV tvOS - #2899

Open
mikelambert wants to merge 1 commit into
postlund:masterfrom
mikelambert:airplay-play-queue-fix
Open

Fix Airplay to modern AppleTV tvOS#2899
mikelambert wants to merge 1 commit into
postlund:masterfrom
mikelambert:airplay-play-queue-fix

Conversation

@mikelambert

Copy link
Copy Markdown

play_url completes a clean handshake against a modern Apple TV and then no picture ever appears, with GET /playback-info returning 500. Reported in #2512, and on M3U8s in #149.

pyatv sends POST /play, which modern receivers no longer use for video. They drive playback through a play queue over POST /command, whose body is a plist wrapping a serialized plist:

{ params: { data: { type: "insertPlayQueueItem",
                    item: { "Content-Location": ..., ... } } } }

setProperty and setRate use the same endpoint. GET /playback-info is not part of such a session at all, so its 500 was never a symptom of failed playback -- it was the device declining an endpoint that does not apply. Treating that 500 as fatal is what tore the session down and removed the picture.

POST /command returns 500 until the sender registers a remote control session via SETUP with a type 130 stream (controlType 1, clientTypeUUID A6B27562-B43A-4F2D-B75F-82391E250194). The receiver answers with no dataPort, so nothing is connected -- requesting the channel is the entire point. This is a different channel from the MRP one in airplay/mrp_connection.py, which uses controlType 2 and a different clientTypeUUID.

Since GET /playback-info cannot be polled, end of media now comes from the receiver itself: it pushes playbackState and notification messages on the event channel, which EventChannel previously acknowledged and discarded. Those messages expect no response beyond that acknowledgement. play_url therefore returns by itself again rather than running until cancelled.

Video sessions must use PTP. NTP looks tempting because it is accepted -- SETUP succeeds, the remote control session registers, the receiver fetches the media and playback starts -- but the session does not survive. No events arrive on the event channel, and playback collapses after roughly twenty seconds, the receiver falling back to whatever was on screen before. Measured on tvOS 26.5: with PTP the picture is still playing after 60s and 26 events arrive over a short session; with NTP, zero events and the picture is gone by 27s despite the media having been fetched in full. Audio streaming is untouched and still uses the NTP setup in _setup_base.

Two receiver behaviours worth recording, because both close the connection with no error response and neither is discoverable from the exchange:

  • playerLoggingID must be at most 6 characters. P/ABCD works, P/ABCDE does not, and it is length rather than case (P/PYATV also fails).
  • Feedback must not be in flight while the play queue commands are issued. POST /feedback is RTSP and CSeq matched, POST /command is HTTP and FIFO matched, and they share a connection.

Receivers that refuse the remote control session still get POST /play and still poll, so devices predating the play queue protocol keep working.

Verified against an Apple TV 4K on tvOS 26.5: Apple's reference BipBop HLS stream, which fails on the current code, plays with audio, and play_url on an 8 second clip returns after 8.5s.

play_url completes a clean handshake against a modern Apple TV and then no
picture ever appears, with GET /playback-info returning 500. Reported in postlund#2512,
and on M3U8s in postlund#149.

pyatv sends POST /play, which modern receivers no longer use for video. They
drive playback through a play queue over POST /command, whose body is a plist
wrapping a serialized plist:

    { params: { data: { type: "insertPlayQueueItem",
                        item: { "Content-Location": ..., ... } } } }

setProperty and setRate use the same endpoint. GET /playback-info is not part of
such a session at all, so its 500 was never a symptom of failed playback -- it
was the device declining an endpoint that does not apply. Treating that 500 as
fatal is what tore the session down and removed the picture.

POST /command returns 500 until the sender registers a remote control session
via SETUP with a type 130 stream (controlType 1, clientTypeUUID
A6B27562-B43A-4F2D-B75F-82391E250194). The receiver answers with no dataPort, so
nothing is connected -- requesting the channel is the entire point. This is a
different channel from the MRP one in airplay/mrp_connection.py, which uses
controlType 2 and a different clientTypeUUID.

Since GET /playback-info cannot be polled, end of media now comes from the
receiver itself: it pushes playbackState and notification messages on the event
channel, which EventChannel previously acknowledged and discarded. Those
messages expect no response beyond that acknowledgement. play_url therefore
returns by itself again rather than running until cancelled.

Video sessions must use PTP. NTP looks tempting because it is accepted -- SETUP
succeeds, the remote control session registers, the receiver fetches the media
and playback starts -- but the session does not survive. No events arrive on the
event channel, and playback collapses after roughly twenty seconds, the receiver
falling back to whatever was on screen before. Measured on tvOS 26.5: with PTP
the picture is still playing after 60s and 26 events arrive over a short session;
with NTP, zero events and the picture is gone by 27s despite the media having
been fetched in full. Audio streaming is untouched and still uses the NTP setup
in _setup_base.

Two receiver behaviours worth recording, because both close the connection with
no error response and neither is discoverable from the exchange:

  - playerLoggingID must be at most 6 characters. P/ABCD works, P/ABCDE does
    not, and it is length rather than case (P/PYATV also fails).
  - Feedback must not be in flight while the play queue commands are issued.
    POST /feedback is RTSP and CSeq matched, POST /command is HTTP and FIFO
    matched, and they share a connection.

Receivers that refuse the remote control session still get POST /play and still
poll, so devices predating the play queue protocol keep working.

Verified against an Apple TV 4K on tvOS 26.5: Apple's reference BipBop HLS
stream, which fails on the current code, plays with audio, and play_url on an 8
second clip returns after 8.5s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant