124 lines
8.8 KiB
Markdown
124 lines
8.8 KiB
Markdown
# ViewMate
|
||
|
||
Очки RayNeo X2 + компаньон на телефон: кино и чат с ассистентом.
|
||
|
||
## Структура
|
||
|
||
```
|
||
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` | игнор | отмена диктовки; вне диктовки — вызов локального ассистента |
|
||
| `SlideBackward` / `SlideForward` | перемотка | в `SESSION_SELECT` — листание сессий; в `TALKING` — скролл текста ответа |
|
||
| `LongClick` | (резерв) | показать / спрятать текст последнего ответа |
|
||
| `TripleClick` | (резерв) | (резерв) |
|
||
|
||
> Жесты распознаёт сам SDK (`TempleAction.Click/DoubleClick/TripleClick/SlideBackward/SlideForward/LongClick`) — своя детекция не нужна (проверено в pair-view, `MainActivity.kt`).
|
||
|
||
## Состояния ассистента (единые для обоих ассистентов)
|
||
|
||
```
|
||
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).
|