66 lines
5.7 KiB
Markdown
66 lines
5.7 KiB
Markdown
# TASK-stt-cancel-reason.md — очки не должны слушать в фоне (цикл SttCancel)
|
||
|
||
## Контекст (отчёт пользователя 23.08 ~21:10)
|
||
«Смотрю видео на фоне, очки рядом лежат — походу он всё это время слушает». По логам очков:
|
||
бесконечный цикл каждые ~31с: `стt отменено — возврат в «слушаю»` → микрофон запущен →
|
||
30с тишины → телефон шлёт SttCancel → очки снова открывают микрофон. При этом очки в лаунчере
|
||
(Activity не в фокусе), процесс жив. Очки даже распознают фоновый звук ТВ. Система начала
|
||
блокировать запись (AppOps RECORD_AUDIO Operation not started).
|
||
|
||
**Корень:** `SttCancel` приходит от ДВУХ разных источников, а очки обрабатывают их одинаково:
|
||
1. **Пустой клик пользователя** (`GlassesServer`, StopStt(cancel=false) с пустым full) — по касяку №2
|
||
очки должны вернуться в «слушаю» (resumeSttListening).
|
||
2. **Watchdog тишины 30с** (`PhoneApp.ensureStt` onSilence30s) — пользователь молчит/ушёл, нужно
|
||
ВЫЙТИ из режима общения (микрофон закрыть, оверлей скрыть), как было в старой логике SttCancel
|
||
(listening=false, thinking=false).
|
||
Сейчас оба шлют голый `SttCancel()`, и очки на любой из них вызывают resumeSttListening → цикл.
|
||
|
||
## Правки
|
||
|
||
### 1. lib-core — протокол: SttCancel получает reason
|
||
`lib-core/src/commonMain/kotlin/pw/binom/viewmate/core/protocol/HostToGlasses.kt`:
|
||
```kotlin
|
||
@SerialName("stt_cancel")
|
||
class SttCancel(val reason: String = "user") : HostToGlasses
|
||
```
|
||
- `reason="user"` — отмена/пустой клик пользователя: очки возвращаются в «слушаю»;
|
||
- `reason="timeout"` — авто-отмена по тишине 30с: очки ВЫХОДЯТ из режима (микрофон закрыть,
|
||
оверлей скрыть, как exitAssistantMode).
|
||
Backward-compatible: старое сообщение без поля десериализуется как reason="user".
|
||
Поправить тест round-trip SttCancel в `lib-core/src/commonTest/.../HostToGlassesTest.kt`.
|
||
|
||
### 2. Телефон — источники
|
||
- `app-phone/.../PhoneApp.kt` onSilence30s (~строка 376): `broadcast(SttCancel())` → `broadcast(SttCancel(reason = "timeout"))`.
|
||
- `app-phone/.../GlassesServer.kt` StopStt(cancel=false) с пустым full: `broadcast(SttCancel())` → `broadcast(SttCancel(reason = "user"))` (явно; можно и дефолт, но явно читаемее).
|
||
- StopStt(cancel=true) (двойной клик, отмена распознавания): тоже `SttCancel(reason = "user")` —
|
||
пользователь остаётся в разговоре, очки возвращаются в «слушаю».
|
||
|
||
### 3. Очки — обработка по reason
|
||
`app-glasses/.../HostConnection.kt` ветка `is SttCancel`:
|
||
- `reason == "timeout"` → выход из режима: `GlassesApp.instance.listening.value = false`,
|
||
`thinking.value = false`, и остановить микрофон. Для этого в `MainActivity` сделать
|
||
**публичный** метод (например `fun onSttTimeout()`), который вызывает логику exitAssistantMode()
|
||
(стоп микрофона + сброс состояний) — или сделать exitAssistantMode() публичной и вызывать
|
||
`GlassesApp.instance.mainActivity?.exitAssistantMode()`. Выбрать чистое решение по коду.
|
||
- иначе (user) → как сейчас: `GlassesApp.instance.thinking.value = false` +
|
||
`mainActivity?.resumeSttListening()`.
|
||
|
||
### 4. Очки — защита «лежат и слушают» (onPause)
|
||
`app-glasses/.../MainActivity.kt` onPause(): сейчас там только пауза кино + sendPlaybackPositionNow.
|
||
Добавить: если режим общения активен (listening || thinking || assistantText.isNotBlank()) —
|
||
выход из режима общения (та же логика exitAssistantMode: sttMic.stop(), сброс состояний).
|
||
Т.е. при потере фокуса Activity (пользователь ушёл в лаунчер) микрофон гарантированно
|
||
закрывается, цикл невозможен.
|
||
|
||
### 5. Сборка, тесты, коммит
|
||
- `./gradlew :lib-core:build :app-glasses:assembleDebug :app-phone:assembleDebug` — BUILD SUCCESSFUL.
|
||
- `git add -A && git commit -m 'Очки: разграничение SttCancel (user/timeout) — выход из режима при тишине 30с, микрофон закрывается при потере фокуса'` (БЕЗ push).
|
||
|
||
## КРИТИЧЕСКИЕ ПРАВИЛА
|
||
- workspace = /root/WORK/view-mate. Пути ТОЛЬКО относительные от корня (app-glasses/...,
|
||
app-phone/..., lib-core/...). НИКОГДА /root/WORK/app-* (без view-mate) — external_directory,
|
||
permission-guard отклонит.
|
||
- НЕ трогать: LocalLlm.kt (GPU), скорость, клавиатуру, автоименование, AssistantGestures.kt,
|
||
матрицу жестов, AssistantOverlay.kt (скролл уже починен).
|
||
- В конце — краткий отчёт: файлы, изменения, сборка, тесты.
|