AirPlay.Player (AirPlay v0.2.0)

Copy Markdown View Source

Streams ALAC-over-RTP audio to an AirPlay/RAOP receiver after the RTSP handshake. Owns three UDP sockets:

  • audio — sends RTP audio (PT 0x60) to device:server_port
  • control — bound on our control_port (6001), sends periodic sync (0xd4)
  • timing — bound on our timing_port (6002), answers the device's timing requests (0xd2 → 0xd3)

Packet formats follow the RAOP wire format (see AirPlay.Rtp). Audio is paced against wall-clock: each tick sends however many 352-sample frames are now due, after an initial ~2 s pre-buffer so the receiver (which delays by the 88200-sample latency) can start cleanly.

Summary

Functions

Start streaming over the already-open sockets. opts: :server_port (required), :control_port (device's control port), :start_seq, :start_ts.

Returns a specification to start this module under a supervisor.

Open the UDP sockets and start answering the receiver's timing requests, WITHOUT streaming yet. opts: :local_control_port (6001), :local_timing_port (6002).

Functions

begin(pid, source, opts)

Start streaming over the already-open sockets. opts: :server_port (required), :control_port (device's control port), :start_seq, :start_ts.

source is where frames come from, either:

  • a list of 352-sample s16le stereo chunks (whole track held in memory), or
  • {:decoder, pid} — pull lazily from an AirPlay.Decoder (bounded memory).

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

start_link(host, opts \\ [])

Open the UDP sockets and start answering the receiver's timing requests, WITHOUT streaming yet. opts: :local_control_port (6001), :local_timing_port (6002).

This must run before the RTSP SETUP: AirPlay receivers (HomePods especially) probe our timing port with an NTP 0xd2 request during SETUP and return "520 Origin Error" if it's unreachable or unanswered. Binding + answering here is what lets SETUP succeed. Call begin/3 once the handshake yields the ports.

stop(pid)