Протокол: SttAudio (PCM s16 16k mono кусками), SttPhrase/SttDone/SttCancel
(host→очки), StopStt(cancel) (очки→host).
Очки: TripleClick — безусловный вызов (кино: пауза + затемнение + слушаем);
во время распознавания Click — завершить (весь текст в консоль, затемнение
уходит, видео на паузе), DoubleClick — отмена; VAD-тишина 30с — автоотмена.
Микрофон (AudioRecord 16k) → SttAudio; на эмуляторе — PCM из filesDir/samples
(куски 3200 + пауза 200мс). Оверлей текста в Binocular (полупрозрачный фон,
текст по центру, в оба глаза). RECORD_AUDIO в манифесте. DEBUG-ресивер
(am broadcast DEBUG_GESTURE type=triple_click|click|double_click).
Телефон: SttStreamer — стриминговый VAD (Silero fp32, окна 512) → фразы →
Whisper-small int8 (executor, не блокирует приём) → SttPhrase на очки;
StopStt → SttDone (полный текст) / SttCancel; watchdog 30с тишины.
Фиксы (найдены на эмуляторе): IllegalThreadStateException — двойной старт
watchdog-потока (thread{}.apply{start()}); decode входящих — try/catch с
логом (не роняет сессию).
Проверено на эмуляторах (5554 очки + 5556 телефон, forward 8080):
triple_click → фразы «Раз, два» и «3 4 5» на очках (оверлей) → click →
«ВЕСЬ ТЕКСТ: Раз, два 3 4 5»; 30с-тишина → «отменено». Тесты: lib-core,
phone, glasses — зелёные.
ViewMate
Очки RayNeo X2 + компаньон на телефон: кино и чат с ассистентом.
Статус реализации (19.08.2026)
| Возможность | Статус |
|---|---|
| Бинокулярный видеорендер на очках (ExoPlayer → EGL-блит в оба глаза), fullscreen | ✅ этап 1 |
| Пульт: пауза/плей/±30с/закрыть (с телефона и из консоли) | ✅ |
| Связка очки↔телефон по WiFi (WS), два эмулятора + adb forward | ✅ |
| Каталог Jellyfin на телефоне (Фильмы/Сериалы/Папки, поиск, постеры, бейджи зеркала) | ✅ |
| Детали: выбор озвучки, статус конвертации, заказ зеркала | ✅ |
| Реальное скачивание с S3 (SigV4, прогресс, докачка с места, персистентный статус) | ✅ |
| Экран «Очки»: связь, батарея/память, пульт | ✅ |
| Синхронизированный звук на телефоне (очки без динамиков) | ✅ 19.08 (diff < 1000 мс, авто-ресинк) |
| Чат с ассистентом (режим CHAT, сессии Hermes, голосовой ассистент) | ❌ заглушка |
| Жесты RayNeo (пауза/перемотка жестом) | ❌ в планах |
| NSD-дискавери телефона (реальные очки находят телефон по сети) | ✅ 19.08: mDNS + скан /24 (на RayNeo multicast режется — работает скан; эмулятор — fallback 10.0.2.2) |
Подробный журнал решений и инцидентов: /root/notes/2026-08-19.md (внутреннее).
Структура
app-glasses/ RayNeo X2 — AR-приложение: видео (ExoPlayer), жесты, связь по WiFi
app-phone/ компаньон на телефон — каталог, дубль-управление, чат
app-host/ отладочный хост — «бэк» без UI, к которому цепляются очки
lib-core/ общий слой (KMP): режимы, состояния, команды, DTO
sketches/ эскизы UI: раскладка экранов + сценарии использования
Эскизы интерфейса
sketches/phone-ui/index.html— эскизы 4 экранов телефона (раскладка компонентов, открыть в браузере)sketches/phone-ui/preview.png— скриншот эскизовsketches/phone-ui/FLOW.md— сценарии использования: как посмотреть фильм, проверить очки, поговорить с ассистентом, что видно при не-сконвертированном/частично скачанном контенте
Режимы
Два режима на очках
| Режим | Состояния | Что происходит |
|---|---|---|
| MOVIE (кино) | PLAYING, PAUSED |
видео на очках (ExoPlayer), звук выбранной дорожки на телефоне, синхронизация по позиции |
| CHAT (общение) | SESSION_SELECT, TALKING |
выбор сессии Hermes, затем разговор в рамках сессии |
Переключение режимов — с телефона (и жестом на очках). Оба дёргают один локальный агент (lib-core).
Каталог — НЕ режим очков
Каталог контента (Jellyfin) живёт только на телефоне (и в будущем — у голосового ассистента, который тоже будет по нему ходить). Выбрал в каталоге → «включить» → очки переходят в режим MOVIE с готовым контентом.
Очки — «экран кинотеатра»: они ждут, когда им скажут «включи», и не листают каталог сами.
Локальный голосовой ассистент (в будущем)
Вызывается в любом режиме (двойной клик в чате), поверх всего. Умеет: ходить по каталогу, включать/ставить кино, переключать режимы, переключать сессии, отвечать на вопросы (локально, без сервера).
Жесты (распознаёт RayNeo SDK, TempleAction)
Вне распознавания (обычный режим)
| Жест (SDK) | Режим КИНО | Режим ЧАТ |
|---|---|---|
Click |
пауза ↔ плей | старт / стоп диктовки |
DoubleClick |
игнор | отмена диктовки |
TripleClick |
безусловный вызов ассистента (пауза видео + затемнение + слушаем) | безусловный вызов ассистента (слушаем) |
SlideBackward / SlideForward |
перемотка | в SESSION_SELECT — листание сессий; в TALKING — скролл текста ответа |
LongClick |
(резерв) | показать / спрятать текст последнего ответа |
Во время распознавания речи (LISTENING)
| Жест | Действие |
|---|---|
Click |
завершить распознавание: вывести весь текст (все фразы) в консоль; затемнение уходит, видео остаётся на паузе |
DoubleClick |
отмена распознавания (текст отбрасывается) |
TripleClick |
игнор |
| VAD молчит 30 с | автоматическая отмена распознавания |
TripleClick — безусловный вызов локального ассистента из любого места (поверх всего). Распознавание идёт по фразам: VAD отсёк фразу — распознал — отобразил на очках — слушаем дальше; ввод считается открытым до Click.
Жесты распознаёт сам SDK (
TempleAction.Click/DoubleClick/SlideBackward/ SlideForward/LongClick) — своя детекция не нужна (проверено в pair-view,MainActivity.kt).TripleClick: если SDK его не отдаёт — детект по таймстампам кликов вonTouchEvent.
Состояния ассистента (единые для обоих ассистентов)
IDLE ──► LISTENING ──► THINKING ──► SPEAKING ──► IDLE
(слушает) (думает) (говорит)
| Состояние | Индикатор |
|---|---|
| IDLE | ничего |
| LISTENING | красная точка ● мигает + время записи (0:07, 0:23…) |
| THINKING | спиннер / точки «думает» |
| SPEAKING | «говорит» (другим цветом / без мигания) |
Один и тот же индикатор для Порфирия и локального ассистента — очкам неважно, кто за ним стоит.
Панель индикаторов
Всегда видна, по центру снизу. Содержимое меняется от режима:
| Режим/состояние | Содержимое панели |
|---|---|
| КИНО | просмотрено / осталось (12:34 / 56:00) + 🔋 батарея + ⏰ часы |
| ЧАТ, диктовка | ● 0:07 (красная точка + время записи) + 🔋 + ⏰ |
| ЧАТ, думает/говорит | индикатор состояния (⏳/🔊) + 🔋 + ⏰ |
| Потеря связи с телефоном | вместо времени — «связь потеряна» (⚠️) + 🔋 |
- Часы — время очков (часы очков, просто время)
- Батарея — очков
- Просмотрено/осталось — из
PlaybackState(positionMs/durationMs) - Если связи с телефоном нет — панель показывает ⚠️ вместо времени (см. Автоматика)
Текст ответа на экране
Длинные ответы можно показать текстом (по LongClick) и скроллить свайпами. Полезно, когда ответ многословный — перечитать спокойно.
Автоматика
| Событие | Действие |
|---|---|
| Очки выключились / экран погас | пауза (видео и аудио) |
| Потеря связи очки ↔ телефон (WiFi) | пауза (и на очках, и на телефоне) + на панели ⚠️ «связь потеряна» вместо времени |
| Связь восстановилась | продолжить с того же места, панель — как обычно |
Связь и поток
очки (RayNeo X2) телефон (компаньон) сервер
┌─────────────────┐ ┌──────────────────┐
│ режимы: │ │ каталог Jellyfin │ Jellyfin
│ MOVIE/CHAT │◄──WiFi────►│ статусы зеркал │──media-mirror API──► координатор
│ жест = switch │ │ заказ конвертации │ → воркеры → S3
│ видео ExoPlayer │ │ переключение │
│ │ │ режимов очков │
│ │ │ чат с Hermes │──assistent/chat──► Hermes API
└─────────────────┘ └──────────────────┘ → TTS → в ухо
- Один локальный агент (на телефоне) — очки и телефон два рендера одного состояния
- WiFi-связь очки ↔ телефон (вместо Bluetooth в старом pair-view)
- Кино: видео 480p H.264 из S3 (media-mirror) на очках, звук выбранной дорожки на телефоне, синхронизация по позиции (очки — мастер, телефон подстраивает аудио)
- Чат: с Hermes через сервер; медиа-интенты («включи сериал X») — напрямую в media-mirror API, без LLM
Медиа-конвейер (внешний, уже живой)
- Координатор:
https://mirror.binom.pw(X-API-Key) — заказ конвертации, статусы, файлы - Формат зеркала:
video.mkv(H.264/NVENC, 480p, без звука) +audio-N.ogg(все дорожки, мета title/language) - S3:
https://s3.binom.pw, bucketmedia, ключиmirror/{itemId}/...; клиентский ключ (read-only) — в конфиге
Полное описание API: media-mirror-api/README.md (https://git.binom.pw/subochev/media-mirror-api).