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
Инструкции по установке самого пакета в вики пока нет. Если знаете источник — дополните страницу.
Установка фикса¶
С компьютера:
| Флаг | Что делает |
|---|---|
--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 надёжнее нажимать с телефона: после возврата Алисы кнопку на колонке могут поймать оба демона.
Что чинит скрипт¶
- AirPlay подключается, но звука нет (или он появляется только после смены трека).
airplaydзависает в функции часов (time()/localtime_r) и перестаёт читать/tmp/sps.fifo, аshairport-syncблокируется на записи. Скрипт патчит 4 байта по смещению 7344 и убирает этот вызов. Побочный эффект: может перестать обновляться время на дисплее (если важно —--no-patch). - Пауза запускает Яндекс.Музыку.
/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, там же отвечает автор.
Три грабли, каждая выглядит как «механизм не работает»:
- Канал. Отправлять только в событийный канал аудио/PTP-соединения. На канале
SETUP (RC)тот же endpoint несёт protobuf MediaRemote — plist там проглатывается. - Канал должен быть уже открыт (
event_channel_fd > 0), иначе отправлять некуда. 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.