# 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`. Ассистент (потоковый): TripleClick → говорите → каждая фраза уходит в LLM (llm.binom.pw), ответ текстом на очки; история диалога — в SQLite на телефоне. Модель/ключ: local.properties llm.model/llm.apiKey. ## Состояния ассистента (единые для обоих ассистентов) ``` 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`, bucket `media`, ключи `mirror/{itemId}/...`; клиентский ключ (read-only) — в конфиге Полное описание API: `media-mirror-api/README.md` (https://git.binom.pw/subochev/media-mirror-api).