ESPHomeHome Assistant

ESPHome AirPlay receiver

An ESP32 or ESP32-S3 with an I2S DAC becomes a Classic AirPlay receiver in ESPHome — native media_source next to Sendspin — so a phone can stream into Home Assistant or Music Assistant.

Difficulty · IntermediateSetup · 45–90 minutesHA · current stable (native API)ESPHome · 2026.9.1 or newer
Get the pack · from $19Have us set it up

Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.

A phone that already knows AirPlay should not need a second app to talk to a bench speaker. This pack flashes an ESP32 (S3 with PSRAM is the comfortable path) plus an I2S DAC as a Classic AirPlay receiver: the node advertises over Bonjour, shows up as an ESPHome media_source, and feeds a speaker_source media player Home Assistant and Music Assistant already understand. Sendspin can sit on the same player so a dedicated speaker is not AirPlay-only.

Entity names and GPIO numbers are substitutions. Do not publish a live hostname, and keep Wi-Fi credentials in secrets.yaml.

AirPlay 2 and guaranteed multi-room sync are out of scope. This is AirPlay 1 (RAOP), one receiver per board.

What problem it solves

You want tap-and-stream from an iPhone, iPad, or Mac onto a cheap ESP32 speaker already in Home Assistant — without a closed-source streamer firmware, and without giving up Music Assistant on the same box.

How it works

  1. I2S speaker first — ESPHome i2s_audio + an external DAC/amp. The speaker must accept 44.1 kHz, 16-bit. A native resampler sits in front if your DAC wants another rate. Get a local media_player playing a HA file before you add AirPlay.
  2. AirPlay as a media_source — the ESPHome-AirPlay external component registers as platform: airplay on ESPHome’s native media_source API (same shape as Sendspin). Decoded PCM goes to the speaker; the component does not seize the I2S bus from other sources.
  3. speaker_source player — one media_player lists sources: [airplay_audio] (and optionally sendspin_audio). A phone starting an AirPlay session selects that source; HA pause/volume use the native player. With remote_control: true, pause / next / previous also try DACP back to the sender when that sender exposes it.
  4. Discovery — the AirPlay name is what phones show in the tap-to-stream list. Bonjour / mDNS must work on the LAN (same VLAN, no client isolation). The pack never prints a device hostname in published YAML.
  5. Sensors — optional active (RTSP session up, including paused), client (sender identity while connected), title / artist / album text sensors, and drop counters (concealed_packets, late_frames, decode_errors, output_drops) for a debug card. Metadata is those sensors, not the media-player title field (ESPHome does not yet pass source metadata through).
  6. Music Assistant — add the Sendspin source on the same player if you want MA queues as well as AirPlay. One pipeline; the native orchestrator stops the previous source when a new one starts.

One receiver per device. Only UDP ALAC at 44.1 kHz / 16-bit stereo / 352 samples per packet is accepted. A MAX98357A is mono: use one channel or a stereo DAC if you care about a real left/right image.

Hardware notes

  • Board. ESP32 or ESP32-S3, ESP-IDF, PSRAM on. The default 512-frame buffer is on the order of 700 KiB of PSRAM per session. S3 boards with octal PSRAM are the least painful. Classic ESP32 without PSRAM will not hold a stable buffer.
  • Amp. MAX98357A (VIN to 5 V, GND, DIN / BCLK / LRCLK, SD held high to enable) is the cheap mono path. A PCM5102A or similar I2S DAC is the stereo / line-out path. Match channel / num_channels to the hardware.
  • Pins. Use substitutions. Avoid USB and boot-strap pins on your module. The pack’s S3 example uses GPIO15 / GPIO16 / GPIO17; change them if your board silkscreen says otherwise.
  • Power. Class-D amps brown out a data-only USB port. Feed 5 V that can actually supply the speaker.
  • Network. 2.4 GHz is fine. Client isolation on the AP will hide the receiver from phones. Do not put the node on a guest SSID.

Snippet

Trimmed from the pack YAML. Names and pins are substitutions. Credentials stay in secrets.yaml.

substitutions:
  friendly_name: Bench speaker
  airplay_name: Bench AirPlay
  bclk_pin: GPIO15
  lrclk_pin: GPIO16
  dout_pin: GPIO17

esphome:
  name: bench-speaker

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: esp-idf

psram:

external_components:
  - source: github://inventor7777/ESPHome-AirPlay
    components: [airplay]

i2s_audio:
  id: dac_bus
  i2s_bclk_pin: ${bclk_pin}
  i2s_lrclk_pin: ${lrclk_pin}

speaker:
  - platform: i2s_audio
    id: dac_speaker
    i2s_audio_id: dac_bus
    i2s_dout_pin: ${dout_pin}
    dac_type: external
    sample_rate: 44100
    bits_per_sample: 16bit
    channel: mono
    num_channels: 1

media_source:
  - platform: airplay
    id: airplay_audio
    name: ${airplay_name}
    active:
      name: AirPlay active
    title:
      name: AirPlay title
    artist:
      name: AirPlay artist
    remote_control: true

media_player:
  - platform: speaker_source
    name: ${friendly_name}
    media_pipeline:
      speaker: dac_speaker
      sources: [airplay_audio]
      sample_rate: 44100
      num_channels: 1

For Music Assistant as well, the pack ships a second YAML with a Sendspin source next to airplay_audio on the same player.

Setup outline

  1. Wire the DAC/amp. Flash a speaker-only ESPHome config and play a local file from HA. Fix pins and brown-outs here, not after AirPlay is in the mix.
  2. Add PSRAM, the external component, the AirPlay source, and the speaker_source player. Adopt the node. Confirm a media_player and the AirPlay active binary.
  3. From a phone on the same LAN, pick the AirPlay name. You should hear audio within a second or two; AirPlay active turns on; title/artist sensors update if the sender provides them.
  4. Optional: add Sendspin, attach the player in Music Assistant, and confirm both sources can take the speaker (one at a time).
  5. Leave a 30-minute stream running. Drop counters should stay near zero on a quiet 2.4 GHz hop; raise buffer_frames to 1024 if you see late_frames / output_drops.

Troubleshooting

  • Phone never sees the receiver. mDNS is filtered (guest Wi-Fi, AP client isolation, or a VLAN without Bonjour reflection). Put the phone and the ESP32 on the same LAN first; do not debug the DAC until the name appears.
  • Name appears, no sound. I2S pins or channel do not match the amp (MAX98357A is mono). Confirm the speaker-only config still plays a HA file.
  • Stutters / late_frames climb. 512-frame buffer is tight on a busy radio, or the board has no PSRAM. Raise buffer_frames, keep the node close to the AP, and do not run verbose logger on the audio build.
  • HA pause does nothing on the phone. Sender did not expose DACP. Local mute still works; remote_control cannot invent a control channel.
  • AirPlay 2 / multi-room is missing. Expected. Use Sendspin / Music Assistant for grouped ESPHome speakers, or a different firmware if you need AirPlay 2 sync.
  • Compile fails on media_source. ESPHome is older than 2026.9.1. Update the compiler; the native API this component uses is not in earlier releases.

Credits

Based on the community project ESPHome AirPlay: a native component that works as a media_source and the inventor7777/ESPHome-AirPlay component (itself a native-API rewrite of jptrsn/esphome-raop). This write-up is an original Super Mini / MAX98357A recipe, not a paste of those pages.

Changelog

v1.0 · Oct 18, 2026: First publish.