324c427c74
STT-модуль для отладки (без UI): WhisperStt (offline whisper int8, CPU), VadSegmenter (Silero VAD, фразы), WavReader (16k mono s16), SttDebug (распознавание wav-файла по фразам с печатью в лог). Вызов: am start --ez stt_debug true (первый wav из filesDir/samples). Модели — в filesDir/models (вне git): small-encoder/decoder.int8.onnx, small-tokens.txt, silero_vad.onnx (fp32!). Грабли, найденные на эмуляторе (Android 15): - Silero VAD int8 (643КБ) молчит в Android-рантайме — нужна fp32 (2.2МБ) - WavReader: littleShort возвращал беззнаковое 0..65535 — сэмплы с DC-offset, whisper давал мусор; теперь знаковый -32768..32767 - VAD требует подачу окнами ровно window_size (целиком — переполнение) - Модели кладутся через adb push /data/local/tmp + run-as cp (внешний каталог приложения на Android 15 не видит adb-файлы) Проверено на эмуляторе (CPU): «раз два три четыре пять» → «Раз, два 3 4 5», грека → почти идеально, masha/anton → 21 фраза, диалог распознан. README: таблица жестов обновлена (TripleClick — безусловный вызов).
160 lines
12 KiB
Markdown
160 lines
12 KiB
Markdown
# 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`, bucket `media`, ключи `mirror/{itemId}/...`; клиентский ключ (read-only) — в конфиге
|
||
|
||
Полное описание API: `media-mirror-api/README.md` (https://git.binom.pw/subochev/media-mirror-api).
|