Плееры как Bluetooth-источник Алисы (bt-proxy)¶
bt-proxy показывает Алисе любой MPRIS-плеер колонки как подключённый Bluetooth-телефон.
Дальше всё делает сама Алиса, штатной логикой Bluetooth:
- отдаёт плееру звук, когда он заиграл, и забирает обратно («Алиса, включи музыку»);
- понимает «дальше», «назад», «пауза», «продолжи», «тише»;
- уступает звук, пока слушает, и показывает анимацию Bluetooth на кольце.
Источники не играют друг поверх друга: последний заигравший ставит остальных на паузу — и другие плееры, и настоящие Bluetooth-телефоны.
MPRIS (org.mpris.MediaPlayer2.*) — стандартный D-Bus-интерфейс плееров Linux, его умеют
shairport-sync (--with-mpris-interface), spotifyd, mpd через mpd-mpris и другие. Писать код
под каждый плеер не нужно: появился на шине — появился у Алисы.
Проверено на Станции Лайт 2 (plum), прошивка 0.369.1.50
С AirPlay (shairport-sync 5.6.2) и настоящим Bluetooth-телефоном одновременно; автозапуск — перезагрузкой. Другие плееры и модели не проверялись.
Как это работает¶
maind узнаёт о Bluetooth по системной D-Bus: объекты службы org.bluez (BlueZ) и
org.bluealsa. Встать рядом нельзя — имя org.bluez у bluetoothd. Поэтому:
bluetoothd,bluealsa,bluealsa-aplayи агент сопряжения переезжают на приватную шину (/tmp/btbus/socket) — достаточноDBUS_SYSTEM_BUS_ADDRESSпри запуске;- на системной шине имена
org.bluezиorg.bluealsaдержитbt-proxyи пересылает всё как есть: вызовыmaind— на приватную шину, ответы и сигналы — обратно, обратные вызовыbluetoothdв объекты клиентов — тому, кто их зарегистрировал. Тела и файловые дескрипторы не разбираются, поэтому настоящий Bluetooth работает как раньше; - MPRIS-плееры на приватной шине
bt-proxyдорисовывает в ответыorg.bluezвиртуальными устройствамиdev_02_A1_*и сам отвечает на вызовы к ним.
Контракт maind¶
Снят записью dbus-monitor во время настоящего Bluetooth-сеанса и сверен с maind.log.
| Объект | Что maind читает или делает |
|---|---|
Device1 |
Name, Address, Connected; телеметрия CurrentRSSI (int16), AFH (string), LQ, TPL (int16) — свойства прошивки Яндекса |
MediaControl1 |
Connected, Player |
…/fd0, MediaTransport1 |
State (idle / pending / active), Volume uint16 0–127 — пишет сам: общая громкость |
…/player0, MediaPlayer1 |
Status, Track (Title, Artist, Album, Duration); вызывает Play, Pause, Next, Previous |
org.bluealsa.PCM1 |
Underruns, SoftVolume — только для настоящих устройств |
Последовательность подключения, на которую maind реагирует:
InterfacesAdded Device1 {Connected: false}
PropertiesChanged Device1 {Connected: true}
InterfacesAdded MediaControl1 {Connected: true}
InterfacesAdded …/fd0 (MediaTransport1), …/player0 (MediaPlayer1)
PropertiesChanged MediaControl1 {Player: …/player0, Connected: true} -> «Bt source connected»
PropertiesChanged MediaTransport1 State: pending -> active
PropertiesChanged MediaPlayer1 Status: playing -> «Bluetooth player is playing», «Take audio focus»
Установка¶
Нужны root по SSH и /data/opt (yse bootstrap его создаёт). Для AirPlay на Лайт 2 всё это
делает установщик — шаги ниже нужны, если ставите bt-proxy для другого плеера.
- Скопируйте на колонку
contrib/bin/bt-proxy(статический ARMv7 musl, 1,2 МБ) иcontrib/bt-proxy/bus.confв/data/opt/btproxy/, сделайтеbt-proxyисполняемым. -
Отключите D-Bus-активацию
org.bluezи поставьте автозапускcontrib/bt-proxy/S55zbtproxy: -
Проверка после загрузки:
Плеер подключается так: запустите его с DBUS_SYSTEM_BUS_ADDRESS=unix:path=/tmp/btbus/socket
(шина пускает только root). Имя, которое увидит Алиса, задаёт ALIASES в S55zbtproxy
по суффиксу MPRIS-имени: ShairportSync=AirPlay,spotifyd=Spotify.
Откат¶
Удалить /etc/init.d/S55zbtproxy, вернуть org.bluez.service.disabled на место
и перезагрузиться.
Переменные bt-proxy¶
| Переменная | Что задаёт |
|---|---|
BT_PRIVATE_BUS |
адрес приватной шины, по умолчанию unix:path=/tmp/btbus/socket |
MPRIS_BUS |
шина плееров, по умолчанию та же приватная |
BT_PROXY_ALIASES |
имена устройств у Алисы, суффикс=имя,… |
BT_PROXY_FAKE |
отладка: сразу устройство с этим именем в статусе Playing, без плеера |
Сборка из исходников — contrib/bt-proxy/build.sh
(Rust, cargo-zigbuild).
Как подключить свой плеер¶
Плееру не нужно знать ни про bt-proxy, ни про Алису — нужны четыре условия.
-
MPRIS (
org.mpris.MediaPlayer2.<имя>, интерфейсorg.mpris.MediaPlayer2.Player):Что Зачем PlaybackStatus:Playing/Paused/Stoppedкто играет: фокус Алисы и пауза остальных Metadata:xesam:title,xesam:artist,xesam:album,mpris:lengthназвание трека Volume0–1 (если есть)общая громкость: MediaTransport1.Volume0–127Play,Pause,Next,Previous,Stop«продолжи», «пауза», «дальше», «назад» сигнал PropertiesChangedпри каждом измененииbt-proxyне опрашивает плеерУстройство появляется у Алисы при первом
Playing, послеStoppedживёт ещё минуту и сразу исчезает, когда плеер уходит с шины. -
Приватная шина. Плеер запускается от root с
DBUS_SYSTEM_BUS_ADDRESS=unix:path=/tmp/btbus/socketи регистрирует MPRIS на «системной» шине. Плееры, умеющие только сессионную шину, переключите в системный режим. - Звук через штатную alsa-lib колонки. Чтобы ответы Алисы звучали поверх музыки, плеер
пишет в
defaultсистемнойlibasound: на Лайт 2 это armhf под glibc не новее 2.30. Entware (aarch64, своя alsa-lib) и готовые сборки под свежие дистрибутивы этого не дают — и не делят dmix с Алисой. Собрать под колонку можно так же, как shairport:contrib/airplay-lite2/Dockerfile(Debian buster, кросс-сборка под armhf). Свой регулятор громкости — softvol в.asoundrc, какasoundrcу AirPlay. - Автозапуск после
S55zbtproxy— свой init-скрипт по образцуS99zairplay-lite2и имя для Алисы вALIASESвS55zbtproxy.
Проверка: в /tmp/bt-proxy.log строка плеер org.mpris.MediaPlayer2.<имя> (<имя у Алисы>),
после воспроизведения в /data/quasar/daemons_logs/maind.log — Bt source connected и
Bluetooth player is playing. Без плеера цепочку можно проверить BT_PROXY_FAKE=<имя>.
Набросок: Spotify Connect через spotifyd (не проверялось)
В spotifyd MPRIS встроен. Готовые релизы armv7 собраны под более новую glibc, поэтому собирать его нужно под колонку (Rust, alsa и dbus из buster). Конфиг:
[global]
device_name = "Yandex Lite 2"
backend = "alsa"
device = "spotify" # softvol в .asoundrc, по образцу airplay
use_mpris = true
dbus_type = "system" # с DBUS_SYSTEM_BUS_ADDRESS приватной шины
ALIASES в S55zbtproxy уже содержит spotifyd=Spotify.
Другие модели¶
Нужен тот же Bluetooth-стек: maind ходит в BlueZ по D-Bus. Проверка на колонке:
ps | grep -E "bluetoothd|bluealsa"
grep -c BluezBluetoothImpl /system/vendor/quasar/maind
ls /etc/init.d/ | grep -iE "blue|bt"
Если всё есть, отличаться будут имена init-скриптов Bluetooth в S55zbtproxy (BT=…) и
флаги демонов. Результаты с других моделей присылайте в issue.
Грабли¶
- D-Bus-активация поднимает второй
bluetoothd. Еслиbt-proxyне держитorg.bluez(перезапуск, падение), системная шина поorg.bluez.serviceзапускает ещё одинbluetoothdна тот же адаптер. Отсюда шаг с.disabledи цикл-супервизор вS55zbtproxy. - Пачка сигналов в одну миллисекунду не доходит.
maindподписывается на сигналы устройства только после обработки егоInterfacesAdded, поэтомуbt-proxyшлёт последовательность подключения с паузами ~400 мс. - Без
MediaControl1 {Player}вPropertiesChangedисточника нет. Если положитьPlayerсразу вInterfacesAdded,maindвидит устройство, но не пишет «Bt source connected». - Призраки после аварийного выхода. Пока
bt-proxyне работает, сигналы об удалении объектов теряются, иmaindдержит мёртвые устройства и транспорты — на них спотыкаются следующие подключения. ПоSIGTERMbt-proxyсам снимает свои устройства; послеkill -9лечится перезапускомmaind --platformd(его поднимет супервизор). - Звук плееров идёт мимо
bluealsa-aplay -S, который разводит только настоящие телефоны. Поэтому арбитраж «последний заигравший побеждает» делаетbt-proxy: ставит на паузу другие MPRIS-плееры и настоящие телефоны (MediaPlayer1.Pause, на телефон — по AVRCP). - «Включи Bluetooth» не нужен. Он открывает видимость для сопряжения новых устройств, а виртуальные устройства в эфир не выходят.