Перейти к содержанию

AirPlay (Shairport Sync)

Станция как приёмник AirPlay 2 для iPhone, iPad и Mac. Скрипт contrib/yandex-station-airplay-fix.sh чинит две известные проблемы уже установленного пакета AirPlay.

Проверено на Станции Мини 3

shairport-sync 4.3.7 (AirPlay 2) + nqptp 1.2.4. На других моделях пути те же, а патч airplayd применяется, только если байты в нужном месте совпадают.

Что нужно заранее

  • root по ADB (шаг 1);
  • установленный пакет AirPlay в /data/shairport (airplay.sh, airplayd, ap2/shairport-sync-ap2).

Откуда взять пакет /data/shairport

Инструкции по установке самого пакета в вики пока нет. Если знаете источник — дополните страницу.

Установка фикса

С компьютера:

./contrib/yandex-station-airplay-fix.sh 192.168.1.50:5555
Флаг Что делает
--status только показать состояние
--with-autostart автозапуск через /etc/init.d/S99zairplay
--no-patch не патчить airplayd, поставить только хуки
--uninstall убрать хуки, вернуть оригинальный airplayd
--force патчить даже при несовпадении байтов (опасно)

После переустановки пакета AirPlay (airplay.sh setup) запустите скрипт ещё раз — конфиг пересоздаётся без хуков.

Использование

На iPhone/Mac в той же Wi-Fi-сети: «Пункт управления» → AirPlay → выберите колонку.

Пока играет AirPlay, Алиса выключена. После паузы или остановки она возвращается примерно через 2 секунды. Play надёжнее нажимать с телефона: после возврата Алисы кнопку на колонке могут поймать оба демона.


Что чинит скрипт

  1. AirPlay подключается, но звука нет (или он появляется только после смены трека). airplayd зависает в функции часов (time()/localtime_r) и перестаёт читать /tmp/sps.fifo, а shairport-sync блокируется на записи. Скрипт патчит 4 байта по смещению 7344 и убирает этот вызов. Побочный эффект: может перестать обновляться время на дисплее (если важно — --no-patch).
  2. Пауза запускает Яндекс.Музыку. /dev/input/event1 читают и airplayd, и maind --platformd. Скрипт ставит хуки shairport-sync: на старте воспроизведения quasar (Алиса) останавливается, на паузе/остановке — запускается снова.

Идеи со Станции 2

Проверено на Станции 2 (yandex,station-midi), на Мини 3 не проверялось

Ниже — не замена рецепту, а три приёма из репозитория про Станцию 2, где AirPlay 2 собирается из исходников. Подробный разбор — docs/03-airplay.md там же. Пути к демонам, порты и способ запуска у каждой модели свои: сверяйте, прежде чем копировать.

Обратное управление: пауза, «дальше», «назад»

DACP для AirPlay 2 не работает: источник не присылает DACP-ID в SETUP и не публикует _dacp._tcp (проверяется с мака: dns-sd -B _dacp._tcp — пусто). Рабочий механизм — plist на канал событий аудио-сессии:

POST /command RTSP/1.0
Content-Type: application/x-apple-binary-plist

{ "type": "sendMediaRemoteCommand", "modernMediaRemoteCommand": <n> }

Номера как у Apple: 0 play, 1 pause, 2 toggle, 3 stop, 4 next, 5 previous. Первоисточник — обсуждение shairport-sync #2262, там же отвечает автор.

Три грабли, каждая выглядит как «механизм не работает»:

  1. Канал. Отправлять только в событийный канал аудио/PTP-соединения. На канале SETUP (RC) тот же endpoint несёт protobuf MediaRemote — plist там проглатывается.
  2. Канал должен быть уже открыт (event_channel_fd > 0), иначе отправлять некуда.
  3. dbus-send без --print-reply. Клиент пишет вызов и сразу закрывает соединение — команда молча отменяется, exit code 0, в логе пусто. На нашей musl/Alpine-сборке это воспроизводится всегда; на Debian и Raspberry Pi OS автор shairport-sync такого не видит — то есть это особенность окружения, но флаг ставить всё равно безвредно.

В development (5.6+) механизм уже в апстриме: remote/remote.c, remote_simple_command() и ap2_event_send_simple_modern_media_remote_command(). Для 4.3.7 из этого рецепта патч придётся портировать; у нас проверено на 5.5.1 (свой патч, scripts/build/airplay2/remote-control.patch) и на 5.7-dev (апстрим, без патча).

Арбитраж звука по TCP-сессии

maind --mediad держит pcmC0D0p постоянно, поэтому приёмник получает EBUSY: AirPlay «подключился», звука нет. Определять «играет» по владению ALSA нельзя — устройством владеет медиа-демон, признак всегда ложный, и круг замыкается: скрипт ждёт приёмник, приёмник ждёт скрипт. Признак — ESTABLISHED-соединение на порт приёмника (/proc/net/tcp, /proc/net/tcp6, состояние 01).

Порт зависит от версии: до 5.6 AirPlay 2 всегда слушал 7000 (1B58) и не смотрел на port из конфига, в 5.6+ берётся значение из shairport-sync.conf (у нас 5000, 1388). Проверять оба, иначе после обновления приёмника сессия не находится и звук держит медиа-демон.

Готовый распорядитель — contrib/audio-owner.sh: при сессии гасит медиа-демон (в цикле — супервизор поднимает его обратно), после сессии перезапускает приёмник, чтобы тот отпустил ALSA, и поднимает демон заново. Сверху вынесены MEDIAD_BIN, AP_PORT_7000, AP_PORT_CONF, каталог приёмника и подсветка — под Мини 3 их надо сверить.

Автозапуск и звук

  • Автозапуск не по номеру в init.d. Скрипт ждёт появления /data и /dev/snd, и только потом поднимает шину D-Bus, avahi, приёмник и распорядителя: иначе приёмник стартует раньше звуковой карты.
  • Свой ALSA_CONFIG_PATH. Системный alsa.conf тянет 32-битные плагины, и musl-бинарник падает с Exec format error.
  • mDNS рядом с maind — раздел «Свой mDNS (avahi) рядом с maind». На Станции 2 хватило того, что avahi получает ровно то имя хоста, которое публикует maind.