Перейти к содержимому

2026-03-25 — Нативный транспорт и null-sink standby

Финальный рывок недели выпустил две функции, к которым всё шло: нативное управление транспортом Sendspin и null-sink standby с автопробуждением. Двадцать один RC-релиз в рамках четырёх стабильных тегов и одного RC доставили их вместе с восстановлением после крашей ALSA, кросс-bridge обнаружением дубликатов, проходом по безопасности и более чем 125 новыми тестами.

Bridge теперь может отправлять команды play, pause, stop, next, previous, shuffle, repeat, mute и volume нативно через роль Sendspin Controller по POST /api/transport/cmd. Раньше всё управление транспортом шло через queue API Music Assistant, добавляя задержку и требуя активного соединения с MA.

Транспортный UI автоматически откатывается на MA queue-команды, когда нативный транспорт недоступен, поэтому операторы со старыми MA-установками не видят изменений в поведении. Кнопки shuffle и repeat обновляются сразу в веб-интерфейсе после успешной нативной команды, соответствуя более отзывчивому ощущению прямого управления.

Соединение по протоколу Sendspin теперь пересылает album, album artist, artwork URL, year, track number, shuffle state, repeat mode и обновления состояния controller, включая supported_commands, group_volume и group_muted. Эти метаданные всегда были доступны в протоколе, но никогда не отображались в status-модели bridge.

Когда запущено несколько экземпляров bridge (типичная ситуация для конфигураций с HAOS и отдельным LXC-деплоем), bridge теперь обнаруживает, когда другой экземпляр уже владеет Bluetooth-устройством. Startup и recovery flow показывают предупреждения о конфликтах, а модальное окно BT scan запрашивает подтверждение перед сопряжением устройства, которое уже занято в другом месте.

Поведение первого запуска стало надёжнее с выверенными дефолтами: startup grace по умолчанию 5 секунд, recovery-banner grace — 15 секунд, PULSE_LATENCY_MSEC — 600, а для вновь добавленных устройств по умолчанию static_delay_ms = -300. Эти значения выбраны на основе production-телеметрии HAOS и устраняют самые частые обращения «работает после одного ручного перезапуска».

Краш ValueError: memoryview assignment: lvalue and rvalue have different structures мог происходить после ALSA underrun и recovery re-anchor внутри subprocess runtime. Bridge теперь защищается от устаревшего закешированного состояния output-frame, так что повторно используемый фрейм из старого формата или цикла коррекции сбрасывается вместо краша.

Несколько ложноположительных guidance-состояний были устранены:

  • Активная аудиотрансляция теперь рассматривается как авторитетная во время кратких переподключений Sendspin control, поэтому переходные окна server_connected=false больше не вызывают предупреждения о потере транспорта, пока колонка продолжает играть.
  • Простаивающие колонки входят в выделенное переходное состояние ma_reconnecting во время плановых переподключений MA metadata вместо отображения предупреждений об отключении.
  • После успешного замещающего переподключения bridge публикует server_connected только после завершения handshake нового WebSocket, предотвращая затирание свежего состояния соединения callback-ом отключения старой сессии.

Исправление совместимости роли visualizer (v2.48.2)

Заголовок раздела «Исправление совместимости роли visualizer (v2.48.2)»

Более новые сборки aiosendspin обнажили черновую роль visualizer@_draft_r1, которая вызывала отказ Music Assistant принять соединение плеера при startup. Bridge больше не анонсирует роль visualizer в ClientHello, и aiosendspin теперь закреплён на 4.3.2 напрямую в requirements.txt для предотвращения транзитивного дрифта зависимостей.

Это была главная функция недели. Когда колонка переходит в idle, daemon продолжает жить на PulseAudio null sink вместо завершения работы. Плеер Music Assistant остаётся видимым, поэтому воспроизведение автоматически возобновляется при запросе — с задержкой Bluetooth-переподключения около 5 секунд вместо 30+ секунд, необходимых для холодного старта daemon.

Система standby включает:

  • Автопробуждение при play — когда MA отправляет play, а колонка в standby, Bluetooth переподключается автоматически.
  • Пробуждение sync-группы — участники группы будят друг друга, поэтому запуск воспроизведения на одной колонке в sync-группе поднимает остальные.
  • Idle disconnect — per-device idle_disconnect_minutes отключает Bluetooth после таймаута тишины для экономии батареи колонки.
  • Взаимное исключение — keep-alive и idle standby взаимоисключающие как в UI, так и в backend; включение одного отключает другое.
  • Null-sink fallback — null sink sendspin_fallback предотвращает попадание осиротевших потоков на случайные Bluetooth-колонки.
  • Отключение rescue-streamsDISABLE_PA_RESCUE_STREAMS выгружает module-rescue-streams PulseAudio при startup для окружений, где дрифт sink постоянный.

UI standby показывает бейдж 💤 на карточках устройств, кнопку-переключатель луна/солнце и переходное состояние «Waking». Фильтр статуса standby в тулбаре позволяет операторам сфокусироваться на активных или спящих устройствах.

Обработка ошибок перешла от перехвата голого Exception к структурированной иерархии: BridgeErrorBluetoothError, PulseAudioError, MusicAssistantError, ConfigError, IPCError. Это сделало тестирование путей ошибок осмысленным и позволило вызывающему коду перехватывать конкретные виды отказов.

Standby-релиз включал проход по безопасности: итерации PBKDF2 повышены до 600K с версионированным форматом хеша, POST /api/config теперь фильтрует через белый список разрешённых ключей, artwork proxy валидирует Content-Type (только image/*), динамические значения onclick используют escHtmlAttr() для предотвращения XSS, capability SYS_ADMIN удалён из конфигов HA addon, а каждый subprocess daemon теперь задаёт уникальное имя приложения PulseAudio для предотвращения путаницы потоков module-stream-restore между колонками.

Набор тестов вырос с ~830 до 959 тестов, покрывая sendspin_client, web_interface, bt_monitor и bt_manager. CI/CD pipeline был унифицирован в единый release workflow: один файл VERSION запускает lint → test → tag → Docker build → HA addon sync.

Вкладка General settings была реорганизована в фокусированные секции, и добавлена отдельная вкладка Audio для настроек PulseAudio. Переключатель экспериментальных функций (browser-local) показывает или скрывает поля room name, room ID и handoff mode. Действия Release и Reclaim переехали в выпадающее меню Device Fleet и список Already Paired.

Первый RC следующего цикла обновил основные зависимости: websockets 13.1 → 16.0 (async API мигрирован на websockets.asyncio.client), waitress 2.1.2 → 3.0.2 и обновления CI actions по всем направлениям.

Нативный транспорт и null-sink standby вместе преобразили bridge из ретранслятора воспроизведения в нечто более близкое к полноценному плееру Music Assistant. Транспортные команды выполняются за миллисекунды вместо round-trip через queue API MA. Standby означает, что колонки просыпаются за 5 секунд вместо 30, делая передачу между комнатами отзывчивой, а не сломанной.

Укрепление безопасности, пользовательские исключения и 125+ новых тестов дали проекту фундамент надёжности, необходимый для поддержки этих более сложных runtime-поведений. А кросс-bridge обнаружение дубликатов закрыло реальную болевую точку операторов, использующих одновременно HAOS и LXC-деплои.

v2.50.0-rc.1 отмечает начало следующего цикла разработки, сфокусированного на модернизации зависимостей и подготовке runtime к работе по backend abstraction для v3, описанной в обновлённом roadmap.