docs(domain H): v2 — медиа/аудио + арбитр ducking, после adversarial-ревью (20 находок) + кросс-док

Новый домен H (медиа/аудио). Ядро — сквозной арбитр аудио (политика
фокуса/ducking поверх PipeWire, role-based), плюс медиаплеер (локальное/BT
A2DP/AVRCP/радио/стриминг). Многоагентный adversarial-ревью: 24 находки,
20 подтверждено (default-refute), все применены.

Ключевое из ревью:
- symphonia НЕ декодирует Opus → отдельный audiopus (libopus, BSD).
- AEC: module-echo-cancel (≠ filter-chain) в audio-plane, не в ассистенте — резолв D §147.
- media.role (арбитраж) vs media.category (Playback/Capture) разведены.
- Кнопки громкости мультируля — uinput→Wayland-input (C/K), не интенты ассистента.
- Crash-safe по двум осям (source: жизнь ноды + проактивный cork по NameOwnerChanged/watchdog; sink: пересчёт при возврате output).
- intra-role media-фокус (один media-продюсер), гистерезис фокуса (анти-pumping), duck = относит. аттенюатор.
- boot аудио-плоскости на Stage 1; отказ-пути плеера (битый файл/обрыв A2DP/ENOSPC/underrun); resume без авто-старта.

Кросс-док: D §147 AEC→; tech-stack (symphonia/audiopus/module-echo-cancel);
hardware §4 (amp-mute-GPIO + FM-тюнер + откр. вопросы); B §4 (amp-mute перед
cut); architecture §9 (аудио-арбитр → H); domains/README (строка H).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 18:08:36 +03:00
parent b9747ee66d
commit 77d9a5a0ee
7 changed files with 267 additions and 7 deletions
+254
View File
@@ -0,0 +1,254 @@
# Домен H — Медиа / аудио
> Вся стандартная мультимедиа (локальное аудио, BT A2DP/AVRCP, радио, стриминг) **и**
> сквозной **арбитр аудио** — политика фокуса/ducking поверх PipeWire, которой подчиняются
> ассистент, телефон, навигация и служебные звуки. First-party ап на SDK (Rust).
Статус: **v2 (на ревью).** v2 — после adversarial-ревью (20 находок).
Связано с: [architecture.md](../architecture.md) (§3 Media, §4 data-plane, §5 карта связей, §9 аудио-арбитраж) · [ipc.md](../contracts/ipc.md) (`Media`, `Assistant`, `Phone`, `Power`) · [security-privacy.md](../contracts/security-privacy.md) (§3 `audio_out`/`audio_in`, §4 гейтинг) · [plugin-sdk.md](../contracts/plugin-sdk.md) (§3 `network`, §4 точки расширения) · [hardware.md](../contracts/hardware.md) (§4 codec/усилитель/mic/тюнер/amp-mute, §3 load-shedding) · [tech-stack.md](../tech-stack.md) (PipeWire/WirePlumber, `symphonia`) · домены **D** (TTS/AEC), **G** (HFP/BT/проекция), **I** (нав-промпты), **C** (мультируль/слоты/distraction), **B** (lifecycle), **J/E/K** (служебные звуки) · [principles.md](../principles.md) (#3,#4,#6,#8,#11,#12,#13)
---
## 1. Назначение и границы
- **Две роли:** (1) **медиаплеер** — источники звука (локальные файлы, BT-аудио, радио, стриминг) +
now-playing/транспорт; (2) **арбитр аудио** — системная политика фокуса/ducking, по которой звук
ассистента/телефона/навигации/служебных сигналов сосуществует с медиа.
- **H — design-owner аудио-политики**, но *enforcement* живёт в **WirePlumber** (база, см. §3) — не в
процессе плеера: падение плеера (#4) не должно ломать маршрутизацию звонка/ассистента.
- **Границы:** H не владеет BT-адаптером/паррингом (домен G/Connectivity; H цепляет только A2DP/AVRCP — §6)
и не генерирует служебные звуки сам — их эмитит владелец фичи (park-beep — иллюстративный пример;
**владелец синтеза служебных звуков открыт, §15**); H лишь **даёт им приоритет роли** `alert`. Не
safety-critical (#1): служебные звуки — информирование, не управление, без регуляторных claim'ов.
- **Plane:** звук — **PipeWire** (+ WirePlumber), data-plane; control/состояние — лёгкий D-Bus (`Media`, §11).
## 2. Аудио-плоскость: роли потоков
- Каждый продюсер звука тегирует свой PipeWire-поток двумя **ортогональными** ключами: **`media.role`**
(use-case — **ОСНОВА арбитража** §3: `media`/`nav_guidance`/`assistant_tts`/`phone_call`/`projection`/
`alert`/`emergency`-резерв) и **`media.category`** (`Playback`/`Capture` — тип обработки, в арбитраже
НЕ участвует; микрофон ассистента — `Capture`, см. §4).
- Роль (`media.role`) — **единственное, что знает политика**: арбитраж по роли, не по «кто это» → развязка
(#4,#9). first-party теги доверенные; стороннему плагину доступен только `media` (через `audio_out`, §11).
- Высокочастотного звука по D-Bus нет (architecture §4): D-Bus несёт лишь состояние/команды.
## 3. Политика фокуса и ducking (арбитр — ядро H)
**Лестница приоритетов playback-ролей** (`media.role`; выше вытесняет ниже):
| Роль | Приоритет | Действие над нижними |
|------|-----------|----------------------|
| `alert` (park-beep, предупреждение) | высш. | короткий **mix-over** (+ duck медиа на длительность) |
| `phone_call` / `projection`-звонок (G) | высокий | **cork** (пауза) медиа; нав-промпт — duck/очередь |
| `assistant_tts` (D) | выше-средн. | **duck** медиа; нав-промпт — очередь |
| `nav_guidance` (I) | средний | **duck** медиа на время промпта |
| `media` (файлы/BT/радио/стриминг) | базовый | — |
*(`assistant_capture` — это `media.category=Capture` (микрофон+AEC §4), а НЕ строка output-лестницы:
capture-нода не «дакает» вывод; дакает медиа именно playback-роль `assistant_tts`.)*
- **Cork vs duck:** cork = пауза с авто-resume по освобождению; duck = временное понижение громкости с
восстановлением. Звонок **корчит** медиа (не микширует); ассистент/нав — **дакают**. **duck —
относительный аттенюатор** поверх текущего пользовательского уровня (master/per-stream), НЕ запомненное
абсолютное значение: изменения громкости мультирулём (§8) во время duck применяются к базовому уровню и
**переживают** un-duck (restore снимает аттенюатор к *текущему* уровню, не к уровню на старте duck); при
`mute`/master=0 duck/un-duck — no-op.
- **Intra-role `media` (единственный media-фокус):** одновременно активен ровно один продюсер роли `media`;
появление нового (плагин-media-source §7, BT-A2DP §6, локальный плеер §5) **корчит** предыдущего держателя
(audio-focus-семантика), а не микшируется (микс в пределах роли — только для `alert`). Координирует
динамическая часть Media-апа (§11 `SetSource`/состояние фокуса). (Ср. арбитраж 0..N видео-источников J §5.)
- **Конкуренция transient:** `assistant_tts` > `nav_guidance` (нав в очередь). Во время звонка медиа
закорчено, ассистент подавлен; `alert` — микшируется поверх всего (короткий, слышен даже в звонке).
- **Гистерезис фокуса (анти-pumping):** переходы duck/cork по `Assistant.StateChanged` сглаживаются —
минимальное окно удержания (hold) перед un-duck (debounce серии idle↔listening от ложных wake-word/VAD,
ср. D §2) + восстановление громкости **фейдом** (release), не скачком. Конкретные attack/hold/release —
реализация (§15 split-политики).
- **Crash-safe restore (две оси):**
- *source-ось:* duck/cork привязаны к **жизни потока-ноды** — исчез продюсер (упал ассистент/нав) →
WirePlumber восстанавливает медиа (#4, ср. ipc §2). **Проактивный cork** (медиа корчится по сигналу
`Phone` ДО появления ноды `phone_call`) на верхнюю ноду не завязан, поэтому его откат гарантируют
дополнительно: (1) исчезновение владельца имени Media-координатора → снятие наложенных корок по
`NameOwnerChanged` (ipc §2); (2) watchdog-timeout, если нода звонка не материализовалась за T мс
(отбой до connect) → авто-un-cork.
- *sink-ось:* при исчезновении output-ноды (load-shed усилителя, hardware §3) наложенные cork/duck —
невалидны относительно этого выхода; при **возврате** output-ноды (напр. после `ShutdownAborted`, B §4)
политика **пересчитывается заново** по живым ролям-продюсерам, а не наследует залипшее состояние.
- **Где живёт:** *статическая* лестница ролей — **конфиг WirePlumber** в базовом образе (поднимается на
**Stage 1**, §9; переживает крэш плеера); *динамическая* координация (проактивный cork, гистерезис,
intra-role, состояние для UI) — компонент Media-апа, подписан на `Assistant.StateChanged`/`Phone`/`Nav`/
`Power` (ipc §34). До Media-апа (v1) хватает статической политики + ролей ассистента.
## 4. AEC и loopback-референс *(резолв D §147)*
- H **публикует monitor/loopback-tap смешанного выхода** как референс для эхоподавления (без него
wake-word во время воспроизведения и barge-in невозможны).
- **AEC — узел audio-графа на базе `module-echo-cancel`** (WebRTC APM, `libspa-aec-webrtc`, BSD — #12) на
пути capture микрофона; референс — monitor-порты выхода. **Не внутри ассистента** (D потребляет уже
очищенную capture-ноду). *(`module-echo-cancel` — отдельный модуль PipeWire, не `module-filter-chain`:
последний — иной механизм (граф LADSPA/LV2/builtin), пригоден лишь для опц. кастомного денойза.)*
→ закрывает «место AEC (в audio-plane vs внутри ассистента)» из D §147 в пользу audio-plane.
- Beamforming/денойз салона (D §148) — отдельно (mic-массив, hardware); AEC ≠ денойз.
## 5. Локальное аудио
- **Библиотека** с USB/`/data`: индексация тегов (artist/album/track/обложка), плейлисты, очередь.
Индексация — **фоном/лениво** (#11), не блокирует UI; БД индекса в приватном хранилище апа.
- **Декодер:** 🟡 **`symphonia`** (чистый Rust: MP3/AAC/FLAC/Vorbis/WAV/ALAC; MPL-2.0 — допустимо по #12).
MP3/AAC/ALAC — за **не-дефолтными фич-флагами**; **AAC** — патенты живы (роялти за декодер) → юр-проверка
перед включением (#12); MP3-патенты истекли (2017). **Opus у `symphonia` НЕ декодируется** → для `.opus`/
Opus-в-`.ogg` (и Opus-интернет-радио §7) — отдельный биндинг libopus **`audiopus`** (BSD, #12). → tech-stack.
- **Gapless/очередь:** декодер-пайплайн/очередь предусматривают **предзагрузку следующего трека** на стыке
(gapless — для альбомов/концертников; crossfade — опц. поверх). Включаем ли — §15.
- **Отказ-пути (симметрично J §3/§4):** битый/недекодируемый файл → **skip трека** + неблокирующая индикация
(не стоп очереди); **ENOSPC при индексации / исчезновение USB-носителя** → gracefully остановить индексацию/
трек с видимой индикацией, не крэш (ср. J §4 «носитель отсутствует → стоп с индикацией»).
- Офлайн (#3): локальное аудио и радио не зависят от сети.
## 6. Bluetooth-аудио (A2DP sink + AVRCP)
- **A2DP sink:** телефон → головблок играет звук телефона. Кодеки: **SBC** (обяз., свободный) + **AAC**;
**aptX/LDAC — проприетарны/лицензируемы → по умолчанию ИСКЛЮЧЕНЫ** (#12), опц. позже при лицензии.
- **AVRCP:** метаданные (трек/исполнитель/позиция) + транспорт (play/pause/next с головблока → телефон).
- **Обрыв A2DP в середине трека** (телефон ушёл из зоны / disconnect-событие от G/Connectivity) →
**cork/пауза транспорта + auto-resume по реконнекту**, не крэш, видимая индикация.
- **Шов с G/Connectivity:** BT-адаптер, паринг и профиль **HFP** (звонок) — **не H** (домен G/Connectivity).
H цепляет A2DP/AVRCP к уже спаренному устройству. Сосуществование A2DP↔HFP на одном устройстве — через
политику (§3): входящий `phone_call` корчит A2DP-`media`. Владелец BlueZ — ◻️ (§15, с G).
- **Проекция (CarPlay/Android Auto)** несёт свои медиа+звонок — её аудио идёт ролями `projection`/`phone_call`;
контроль/протокол проекции — **G**. H лишь маршрутизирует звук по политике.
## 7. Радио, стриминг, плагин-источники *(later)*
- **FM-радио (+ RDS):** 🟡 **требует тюнера, которого нет в hardware §4** → добавить FM-тюнер в периферию
**или** отказаться от радио (§15 → hardware). DAB для РФ — вне скоупа. RDS-station/TMC — задел (TMC мог бы
кормить нав I). Фаза v3.
- **Стриминг:** провайдер-агностично (#8), бэкенды сменные; **креды — свои у пользователя** (как LLM в
D §5). RU-сервисы (Yandex Music/VK/Звук). **DRM/ToS** некоторых сервисов — open (§15). Сеть — через
`network`-capability (plugin-sdk §3, host-allowlist). Underrun/исчерпание буфера → `PlaybackState=buffering`
(§11) с rebuffer + таймаут → стоп с индикацией (ср. online-degradation D §5). Фаза v3.
- **Интернет-радио** (HTTP/Icecast-потоки) — лёгкий вход, только `network` (Opus-потоки → `audiopus`, §5). Фаза v3.
- **Источники-как-плагины (#9):** H даёт точку расширения «media-source» — плагин регистрирует источник
звука (стриминг/подкасты/интернет-радио), как J — 0..N видео-источников. `audio_out`+`network`; подчиняется
intra-role media-фокусу (§3). → F/plugin-sdk.
## 8. Вывод: громкость, маршрутизация, усилитель
- **Выход:** PipeWire → I2S codec → усилитель (hardware §4). Одна зона салона (MVP); мульти-зона
(задние) — вне скоупа/later (§15).
- **Громкость:** master + per-stream; mute; анти-clipping лимитер на миксе. 🟡 **Кривая — перцептивная
(логарифмическая)** дефолт (линейная даёт неюзабельную ручку в шуме салона); реализуется в
PipeWire-graph/WirePlumber (→ tech-stack). Balance/fader (L/R) и эквалайзер — вне скоупа MVP (одна зона), задел later (§15).
- **Два тракта команды громкости** (разные каналы, одно действие): (1) физические кнопки **мультируля**
приходят как key-события через uinput → libinput → Wayland-input сфокусированному Media-клиенту (C §9,
ADC-лесенка/проксированный CAN — K §3) — это **НЕ интент ассистента**; (2) голосовое «громче/тише» —
локальный интент D §6 (после STT, без LLM).
- **Pop-suppression:** усилитель **mute при boot/shutdown** (anti-pop); enable/mute-секвенс amp — владелец
физической линии **hardware §4** (mute-GPIO), владелец порядка **B §4** (mute-then-cut в teardown). При
power-loss усилитель **сбрасывается load-shedding'ом** (hardware §3) — H обязан корректно отработать
исчезновение output-ноды (без зависания, §3 sink-ось), не пытаться «доиграть».
## 9. Медиа и lifecycle (шов с B)
- **Boot аудио-плоскости:** PipeWire+WirePlumber и статическая лестница ролей (§3) поднимаются на **Stage 1**
(раньше Media-апа; сам Media — Stage 2, architecture §6), чтобы маршрутизация/политика были доступны до
прогрева плеера. **До Stage 1 звука нет** (как голоса нет в boot-окне, D §8). Роль `alert` доступна с
поднятия WirePlumber (Stage 1); **ранний park-beep на Stage-0-реверсе** (до аудио-плоскости) — за её
пределами, потребует отдельного раннего звукового пути (◻️ §15 → J/E/B; park-beep informational, #1).
- **`ShutdownImminent`/ACC-off (ipc §3, B §4):** остановить воспроизведение, **mute amp до PONR** (порядок —
B §4, §8), сохранить now-playing (источник/трек/позиция/очередь) в Settings.
- **Resume после рестарта (later):** восстановить контекст **без авто-старта**, если источник недоступен
(BT ещё не переподключился §6, USB вынут, нет сети §7) — показать now-playing как `paused`/`unavailable`
(§11) и ждать появления источника; авто-play — только когда источник готов; **не висеть в `buffering`**.
- **Sleep:** аудио выключено; медиа **не держит систему бодрствующей** вопреки power-политике (#5, B).
Воспроизведение — в `running`/`accessory`, не в `sleep`/`battery_cutoff`.
- **Память (a-base §8):** буферы декода/стриминга малы vs видео; медиа — throttleable, низкий приоритет OOM.
## 10. Driver-distraction (#6, владелец — C)
- На скорости выше порога: now-playing **упрощается**, просмотр библиотеки/набор текста — блокируются/
сворачиваются; приоритет — голосовому управлению (D). Правило — единое через Shell/SDK (C §7), H подчиняется.
## 11. IPC / интерфейсы
- **`ru.shturman.Media`** (ipc §4, домен H): `Play()`/`Pause()`/`Next()`/`Prev()`/`Seek(pos)`,
`SetSource(id)`, `ListSources() → [(id, kind)]` (включая плагин-media-source §7, не только встроенный
плеер); сигнал `NowPlayingChanged`; properties `PlaybackState` (`stopped`/`playing`/`paused`/`buffering`/
**`unavailable`** — источник пропал/не готов), `NowPlaying` (метаданные), `Volume`.
- **Аудио-политика:** наружу — **состояние** (`AudioFocusChanged(role)` / property `ActiveFocus`,
`DuckingActive`) для UI; запрос фокуса — **в основном неявно** (роль потока + WirePlumber). Явная
D-Bus-форма focus-request — ◻️ при необходимости (§15).
- **Гейтинг:** воспроизведение плагином — `audio_out` (security-privacy §3: только playback-ноды, не
capture/monitor чужих); микрофон/loopback-монитор — **никогда** стороннему по `audio_out` (это `audio_in`).
- **UI:** now-playing — декларативный тайл (plugin-sdk §4.1) + богатый медиа-экран как **Wayland-поверхность**
в слоте shell (C §4, slot-протокол).
## 12. Dev-симулятор (#13)
- **Fake-аудио-источники** поверх PipeWire (dev-environment): синтетический медиа-поток, мок BT-A2DP-ноды,
мок нав-промпта/TTS/звонка для матрицы ducking (§3); мок исчезновения output-ноды (load-shed / sink-ось
restore); **отказ-сценарии §5/§6/§7** (битый файл, обрыв A2DP, ENOSPC, stream-underrun); дребезг
`StateChanged` (гистерезис §3). Сценарии «медиа↔звонок↔ассистент» и «два media-источника» (e2e) — отсюда. → dev-environment.
## 13. Функции
| функция | MVP/later | зависит от | фаза |
|---------|-----------|------------|------|
| Аудио-плоскость + роли (PipeWire `media.role`, Stage 1) | **MVP** | PipeWire/WirePlumber | v1 |
| Политика фокуса/ducking (лестница, crash-safe 2 оси, гистерезис) | **MVP** | WirePlumber, D (state) | v1 |
| AEC (`module-echo-cancel`) + loopback-референс | **MVP** | PipeWire, D | v1 |
| Громкость/mute (master + per-stream, кривая, мультируль) | **MVP** | C/K (ввод) | v1 |
| Вывод → codec/усилитель + pop-suppression | **MVP** | hardware, B | v1 |
| Локальный плеер (библиотека + декодер + отказ-пути) | **MVP** | storage, `symphonia`/`audiopus` | v2 |
| Now-playing UI + транспорт (тайл + поверхность) | **MVP** | C (слоты), Shell | v2 |
| BT A2DP sink (SBC/AAC) + AVRCP (+ обрыв-handling) | **MVP** | BT-адаптер (G/Connectivity) | v2 |
| Resume playback после рестарта (без авто-старта) | later | Settings, B | v2 |
| Gapless-воспроизведение | later | `symphonia` | v2 |
| FM-радио (+ RDS) | later | **тюнер (нет в hardware §4)** | v3 |
| Стриминг (провайдер-агностик, креды юзера) | later | network, plugin-sdk | v3 |
| Интернет-радио (HTTP/Icecast) | later | network | v3 |
| Media-source точка расширения (плагины) | later | plugin-sdk, F | v3 |
| Мульти-зона / balance-fader-EQ / BT A2DP source | later | hardware/BT | later |
## 14. Зависимости
- **Вниз:** hardware (§4 codec/усилитель/mic/тюнер/amp-mute-GPIO, §3 load-shedding), PipeWire/WirePlumber +
`module-echo-cancel` (tech-stack), storage (`/data`, библиотека), `symphonia`/`audiopus`/WebRTC-APM (tech-stack).
- **Вбок:** **D** (роли `assistant_tts`/`capture`, AEC-потребитель, `StateChanged`), **G** (HFP/BT-паринг/
проекция — звонок корчит медиа), **I** (нав-промпты, роль `nav_guidance`), **B** (ShutdownImminent/sleep/ACC,
load-shed, amp-mute-порядок), **C** (мультируль-ввод, слоты now-playing, distraction-owner), **J/E/K** (служебные `alert`).
- **Вверх:** медиа-UI и состояние фокуса для Shell; расширяемо плагинами (`audio_out` + media-source).
## 15. Открытые вопросы
- 🟡 **Декодер/кодеки:** `symphonia` (рек.) + **AAC-патент** (роялти) — юр-проверка; Opus → `audiopus`; SBC/AAC
по BT, **aptX/LDAC исключены** (#12). → tech-stack.
- 🟡 **FM-тюнер:** добавить в hardware §4 периферию **или** отказаться от радио. → hardware.
- 🟡 **Владелец BlueZ/паринга** (Connectivity vs G vs shared) + сосуществование A2DP↔HFP. → G/Connectivity.
- 🟡 **enable/mute-секвенс усилителя (anti-pop):** owner GPIO — hardware §4 (mute-GPIO), owner порядка — B §4. → hardware + B.
-**AEC** — узел audio-plane (`module-echo-cancel`/WebRTC APM), референс = output-monitor, не в ассистенте;
loopback-tap — §4. → резолв §4 (D §147; синхронизировано в d-assistant §10).
- ◻️ **Ранний звук на Stage-0-реверсе** (park-beep до поднятия аудио-плоскости) — нужен ли отдельный путь. → J/E/B.
- ◻️ **Split политики:** сколько в статическом WirePlumber vs Rust-координаторе (+ crash-safety проактивного
cork, тайминги гистерезиса/фейда дакинга). → реализация.
- ◻️ **Явный focus-request API** (D-Bus) vs только неявные роли. → реализация/ipc §4.
- ◻️ **Gapless/crossfade** — включаем ли в локальный плеер (предзагрузка трека). → реализация (v2).
- ◻️ **Стриминг:** какие RU-сервисы, **DRM/ToS**, официально vs плагин. → later.
- ◻️ **Генерация служебных звуков** (park-beep/предупреждения): ассеты и владелец синтеза (J/E vs общий chime-сервис H). → J/E + H.
- ◻️ **Мульти-зона** (задние) + balance/fader + EQ — подтвердить «вне скоупа MVP». → later/hardware.
---
## Журнал решений (домен H)
| Решение | Выбор | Дата |
|---------|-------|------|
| Роль H | медиаплеер + **design-owner аудио-политики**; enforcement в WirePlumber (не в плеере) | 2026-06-22 |
| Модель арбитража | `media.role`-теги (playback) + лестница; `media.category` (Playback/Capture) — не арбитраж; intra-role media — единственный фокус | 2026-06-22 |
| Ducking | cork (звонок) vs duck (ассистент/нав); duck — относительный аттенюатор; гистерезис/фейд (анти-pumping) | 2026-06-22 |
| Crash-safe | две оси: source (жизнь ноды + проактивный cork по `NameOwnerChanged`/watchdog) + sink (пересчёт при возврате output) | 2026-06-22 |
| Где политика | статика — конфиг WirePlumber (Stage 1, переживает крэш); динамика — координатор Media-апа | 2026-06-22 |
| AEC | `module-echo-cancel` (WebRTC APM) в audio-plane (не в ассистенте); H даёт loopback-референс — **резолв D §147** | 2026-06-22 |
| Декодер | `symphonia` (Rust, MPL-2.0; AAC-патент на юр-проверку; Opus НЕ поддержан → `audiopus`); aptX/LDAC исключены (#12) | 2026-06-22 |
| BT-аудио | A2DP sink (SBC/AAC) + AVRCP; обрыв → cork+auto-resume; паринг/HFP — G/Connectivity (шов) | 2026-06-22 |
| Радио/стриминг | later (v3); FM требует тюнера (нет в hardware); стриминг — креды юзера, провайдер-агностик | 2026-06-22 |
| Lifecycle | boot аудио-плоскости Stage 1; стоп+mute-amp на ShutdownImminent; resume без авто-старта; не держит wake | 2026-06-22 |