Repository navigation
AirPlay 2 remote control: the mechanism is sendMediaRemoteCommand on the session event channel
#2262
Replies: 5 comments 6 replies
|
Many thanks for this fantastic work! It's great to finally see progress on AirPlay 2 remote control. Just in the last few days, we actually have announced remote control for AirPlay 2 on the The process of discovering these techniques has been, by times, frustrating, tedious and laborious, so your discoveries are really interesting. It may also be that our implementations are too complicated -- there hasn't been much experimentation done on simplifying them, and your code may be helpful there. The command availability information you've identified will feed into the MPRIS (BTW, the updated metadata reader and the updated D-Bus test client will pretty print the new command and metadata information.) The Remote Control connections are a real puzzle, and we have made zero headway on that front; any ideas and results would be very welcome. |
|
Field report: AirPlay 2 remote control on Setup: a Raspberry Pi 4 on Raspberry Pi OS trixie (arm64) with the PipeWire backend, D-Bus on the session bus, and NQPTP Before (5.2.2), same receiver: AirPlay 2 sessions came with no DACP-ID/Active-Remote and no Results on 5.6-dev, AirPlay 2:
Observations that may help:
Thanks for the work on this. Happy to test further builds. |
|
Great stuff -- many thanks for this report!
Working on some other aspects of Shairport Sync right now, but we'll circle back soon. |
|
Thanks — the mechanism works for us too, and we got a full working recipe out of it. Build: shairport-sync 5.5.1, AirPlay 2, on a rooted Yandex Station (aarch64/musl). Confirming your finding: the plist goes on the audio/PTP connection's event port. On the Two pitfalls that cost us a lot of time and are not obvious from the protocol alone:
Smaller notes, in case someone builds it for musl:
Verified on macOS and on iPhone: next/previous switch tracks, toggle pauses and resumes. We drive it from the speaker itself (a small script, a web panel and voice commands), so the receiver is the one issuing the command. Our notes, including the code patch and the build script: https://github.com/derangedabbot/yandex-station-midi-airplay (docs/03-airplay.md, step 6, and scripts/build/airplay2/remote-control.patch). Thanks for digging into the framework — the |
|
Many thanks for the update! A couple of things for other readers:
The configuration flag is, in fact,
That is not the case on Debian / Ubunbtu / Raspberry Pi OS, etc. Perhaps it's a peculiarity of your OS or libraries?
Just for clarity, as best I understand it, the plumbing and patching you refer to is of your system and scripts, not Shairport Sync itself. Is that correct? Thanks again. |
Uh oh!
There was an error while loading. Please reload this page.
The README says:
I think I found it, and it works here — play, pause, next track and previous track all
take effect on the sending iPhone.
The message
sent with
ap2_event_port_post_command()on the session's event channel — the sameone
ap2_event_send_update_info()already uses.<n>is a MediaRemote command number:0play,1pause,2toggle,3stop,4next track,5previous track. These are the same numbers the sender itselfadvertises: on connect it POSTs
updateMRSupportedCommandswithmrSupportedCommandsFromSender, which Shairport Sync already forwards to the metadatapipe as
ssnccopl. On my iPhone commands 0–5 are all reported as enabled, so thereceiver can even know in advance which ones will work.
One trap worth documenting
AirPlay 2 opens two connections here. Sending this on the audio/PTP connection's
event channel works. Sending it on the remote-control connection (
SETUP (RC),airplay_stream_category == remote_control_stream) does not — that channel carrieslength-prefixed MediaRemote protobuf messages instead, which is a separate
mechanism. I spent a long evening on the protobuf path before realising the plist path
was the right one. Writing protobufs to the audio connection's event channel got my
receiver dropped from the session repeatedly.
Where it comes from
Not from guessing at the phone — from Apple's own code. On macOS 26,
AirPlayReceiver.frameworklives only in the dyld shared cache. Searching it (withgrep -a; without-agrep silently reports nothing on binaries) forupdateMRSupportedCommandslands in a region that contains the whole receiver-sidevocabulary:
airplayReqProcessor_requestProcessCommandwith the commands a receiver accepts:duckAudio,unduckAudio,mute,unmute,changeRelativeVolume,performPWDKeyExchange,updateMRNowPlayingInfo,updateMRSupportedCommands,setMRInfo,mrInfo,setSenderDisplayLatencyMs,updateMRPlaybackState,updateMRNowPlayingClient,fadeAudioAirPlayReceiverSessionSendMediaRemoteCommandnext to the stringssendMediaRemoteCommandandmodernMediaRemoteCommand, and the log format"Sending MediaRemote command (MR: %lld) (AP: %'C) back to sender."AirPlayReceiverSessionSendCommandForObjectnext toPOST,/command,RTSP/1.0,application/x-apple-binary-plist,startTicks,eventClientIDThe plist shape (keys as siblings of
type, not nested underparams) is readablefrom neighbouring commands in the same table:
setScreenRecordingState/state,updateMC2UCStatus/mc2ucProbeBurstID/mc2ucStatus/mc2ucPktCnt.Incidentally:
ap2_event_send_modern_media_remote_command()is already declared inap2_event_message_handler.hbut never defined — the name matches the Apple stringexactly, so I suspect you had already seen it.
Implementation
The only other thing needed is picking the right connection: one whose
event_channel_fd != 0and whoseairplay_stream_categoryis notremote_control_stream.Tested with
0c53cf5),--with-airplay-2, Alpine Linux x86I have not tested other senders, older iOS versions, or macOS as a sender, and I have
not looked at what a
SEND_COMMAND_RESULTwould look like — nothing comes back beyondthe RTSP
200 OK.Happy to open a PR if that is useful. I did not file this as an Issue since blank
issues are disabled and this is not a bug report.
All reactions