AirPlay (AirPlay v0.1.0)

Copy Markdown View Source

A pure-Elixir AirPlay (RAOP) audio sender — discover receivers on the LAN and stream lossless audio to them, with no native dependencies (just :crypto, :gen_udp and :gen_tcp).

Targets classic AirPlay 1 / RAOP receivers (shairport-sync, AirPort Express, and — verified — Apple HomePods), streaming unencrypted ALAC over RTP with the NTP-style timing/sync the receiver requires.

Quick start

# Find receivers on the network (mDNS browse of _raop._tcp)
AirPlay.discover()
#=> [%{name: "Office", host: "172.16.42.35", port: 7000}, ...]

# Stream a file (decoded via ffmpeg) at 40% volume
{:ok, session} = AirPlay.play("172.16.42.35", "/music/track.flac", volume: 40)
AirPlay.set_volume(session, 25)
AirPlay.stop(session)

# Or stream raw PCM you already have (44.1kHz, s16le, stereo interleaved)
{:ok, session} = AirPlay.play_pcm("172.16.42.35", pcm, volume: 40)

play/3 requires ffmpeg on the PATH (used to decode the source file to PCM). play_pcm/3 has no external dependency.

Experimental AirPlay 2 building blocks (transient pairing, ChaCha20-Poly1305 encrypted control channel, binary plist, SETUP) live under AirPlay.V2; the control plane is verified against real HomePods but audio rendering is not yet complete, so use the AirPlay 1 API above for playback.

Summary

Types

A running playback session (the AirPlay.Cast GenServer pid).

Functions

Browse the LAN for AirPlay/RAOP receivers for timeout_ms (default 2500).

Decode the audio file at path (via ffmpeg) and stream it to host.

Stream a raw PCM buffer (44.1 kHz, signed 16-bit little-endian, stereo, interleaved) to host. Same options as play/3.

Set the playback volume (0–100) on a running session.

Stop a running session and tear down the RTSP connection.

Types

session()

@type session() :: pid()

A running playback session (the AirPlay.Cast GenServer pid).

Functions

discover(timeout_ms \\ 2500)

@spec discover(non_neg_integer()) :: [map()]

Browse the LAN for AirPlay/RAOP receivers for timeout_ms (default 2500).

Returns a list of %{name: String.t(), host: String.t(), port: pos_integer()}.

play(host, path, opts \\ [])

@spec play(String.t(), Path.t(), keyword()) :: {:ok, session()} | {:error, term()}

Decode the audio file at path (via ffmpeg) and stream it to host.

Options:

  • :volume — 0–100 (default 25)
  • :port — RTSP port (default 7000)

Returns {:ok, session} where session is a pid you pass to set_volume/2 and stop/1; it streams in the background and stops itself when the track ends.

play_pcm(host, pcm, opts \\ [])

@spec play_pcm(String.t(), binary(), keyword()) :: {:ok, session()} | {:error, term()}

Stream a raw PCM buffer (44.1 kHz, signed 16-bit little-endian, stereo, interleaved) to host. Same options as play/3.

set_volume(session, volume)

@spec set_volume(session(), 0..100) :: :ok

Set the playback volume (0–100) on a running session.

stop(session)

@spec stop(session()) :: :ok

Stop a running session and tear down the RTSP connection.